一、获取 Bean
- Spring 提供了多种实例化Bean的方式:【只是表现形式不同,底层都是通过构造方法创建对象的】
- 通过构造方法实例化 【最简单的方式直接声明bean】
- 通过简单工厂模式实例化 【定义一个简单模式工厂,然后通过工厂的静态方法获得Bean】
- 通过factory-bean实例化 【定义一个方法工厂,通过实例方法(需要创建对象才能调用)获取Bean】
- 通过FactoryBean接口实例化 【我们工厂类实现了FactoryBean 接口,声明工厂类的bean就能返回特定Bean的实例】
$ 通过构造方法实例化
- 需求:我们创建一个普通的Bean,然后在配置文件中声明一下,最后测试是否可以成功获得Bean
编写一个Bean类 SpringBean.java
package com.powernode.spring6.bean;
/**
* @author Bonbons
* @version 1.0
*/
public class SpringBean {
}
编写我们的配置文件 spring.xml
<!--
Bean对象实例化方法一: 通过声明bean,给出全限定类名,spring会自动调用该bean的无参构造方法来实例化Bean
-->
<bean id="sb" class="com.powernode.spring6.bean.SpringBean"/>
编写测试方法 SpringBeanTest.java
@Test
public void testInstantiation1(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
SpringBean sb = applicationContext.getBean("sb", SpringBean.class);
System.out.println(sb);
}
$ 通过简单工厂实例化
- 在之前工厂模式专题我们可以知道,简单工厂就是通过一个静态方法来获取Bean对象
编写我们的测试类 Gun.java
package com.powernode.spring6.bean;
/**
* @author Bonbons
* @version 1.0
*/
public class Star {
public Star() {
System.out.println("调用了Star的无参构造方法。");
}
}
编写我们的简单工厂类 StarFactory.java
package com.powernode.spring6.bean;
/**
* @author Bonbons
* @version 1.0
*/
public class StarFactory {
// 在简单工厂中,通过静态方法获取我们的产品对象
public static Star get(){
return new Star();
}
}
编写我们的配置文件
<!--方法二:通过简单工厂模式实例化Bean
我们需要指定使用哪个工厂,指定调用工厂的哪个静态方法获取Bean
这个Bean实际还是自己 new 的,只不过是通过Spring的bean来获取
底层还是调用的构造方法,只是外在的展示形式不同
-->
<bean id="starBean" class="com.powernode.spring6.bean.StarFactory" factory-method="get" />
编写我们的测试方法
@Test
public void testInstantiation2(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Star star = applicationContext.getBean("starBean", Star.class);
System.out.println(star);
}
$ 通过 factory-bean 实例化
- 利用工厂方法模式来获取Bean对象,与简单工厂模式的区别在于 获取对象的方法方法不是静态的 【需要创建这个工厂的Bean对象】
- 需求:我们创建一个类和对应的工厂,在配置文件中演示如果通过工厂方法模式实例化我们的Bean
编写我们的 Gun.java
package com.powernode.spring6.bean;
/**
* 具体产品角色
* @author Bonbons
* @version 1.0
*/
public class Gun {
public Gun() {
System.out.println("调用了Gun的无参构造方法。");
}
}
编写我们的工厂方法类 GunFactory.java
package com.powernode.spring6.bean;
/**
* 具体工厂角色
* @author Bonbons
* @version 1.0
*/
public class GunFactory {
// 方法是实例的 >> 需要创建对象才能调用
public Gun get(){
return new Gun();
}
}
配置我们的 bean
<!--
方法三:通过工厂方法模式实例化Bean
因为需要创建工厂的Bean,
通过 factory-bean 属性告诉Spring使用哪个工厂的对象,
然后通过 factory-method告诉Spring调用哪个方法获取Bean
-->
<bean id="factory" class="com.powernode.spring6.bean.GunFactory" />
<bean id="gunBean" factory-bean="factory" factory-method="get"/>
- 第一步,我们要声明我们工厂的bean,因为要用到这个对象
- 第二步,不需要指定class属性,通过 factory-bean 和 factory-method 两个属性指定了使用哪个对象的什么方法获取Bean
编写测试方法:
@Test
public void testInstantiation3(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Gun gun = applicationContext.getBean("gunBean", Gun.class);
System.out.println(gun);
}
- 为什么此处还打印了 调用了Star的无参构造方法。?
- 因为我们生命Bean的时候scope使用了默认的 singleton >> 单例模式
- 创建 Bean 对象的时机是初始化上下文的时候,也就是解析XML文件时就给XML文件中的所有Bean创建了对象。
$ 通过 FactoryBean 实例化
- 属于第三种方法的简化版,我们只要将工厂实现了FactoryBean接口,就不用去配置 factory-bean 和 factory-method 两个属性
- 在我们声明工厂Bean的时候,就会返回一个我们指定的普通Bean的实例
- factory-bean会自动指向实现FactoryBean接口的类,factory-method会自动指向**getObject()**方法
- 需求:通过一个类和对应的工厂类来演示
定义我们的具体角色类 Person.java
package com.powernode.spring6.bean;
/**
* @author Bonbons
* @version 1.0
*/
public class Person {
public Person() {
System.out.println("调用了Person的无参构造方法。");
}
}
定义我们的具体工厂类 PersonFactory.java
package com.powernode.spring6.bean;
import org.springframework.beans.factory.FactoryBean;
/**
* 工厂Bean >> 可以获得普通Bean
* @author Bonbons
* @version 1.0
*/
public class PersonFactoryBean implements FactoryBean<Person> {
@Override
public Person getObject() throws Exception {
// 获取我们的Bean对象
return new Person();
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
// 在接口中就实现了这个方法,默认为 true >> 单例
return FactoryBean.super.isSingleton();
}
}
编写配置文件
<!--
方法四:通过FactoryBean接口来实例化我们的Bean
属于方法三的简化形式,我们的工厂方法模式实现了这个接口,在配置的时候就不需要
指定factory-bean、factory-bean两个属性
直接通过创建工厂Bean就能直接返回一个具体的普通Bean的对象
-->
<bean id="person" class="com.powernode.spring6.bean.PersonFactoryBean" />
编写我们的测试文件
@Test
public void testInstantiation4(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Person person = applicationContext.getBean("person", Person.class);
System.out.println(person);
}
$ BeanFactory 和 FactoryBean 的区别
-
BeanFactory
- 工厂
- Spring IoC 容器的顶级对象,被称为“Bean工厂”,负责创建Bean对象
-
FactoryBean
- Bean
- 能够辅助Spring实例化其它Bean对象的一个Bean。
-
在Spring中,Bean可以分为两类:
- 普通的Bean
- 工厂Bean
$ 注入 Date 针对方法四的实现案例分析
- 我们知道 Date 可以作为简单类型也可以作为非简单类型使用
- 作为简单类型时,通过 value 传递参数值要采用特定的语法格式
- 作为非简单类型时,我们通过 ref 传入指定日期的 Bean
- 需求:接下来我们演示如何通过工厂类实现非简单类型日期的注入
编写我们的普通角色类 Person.java
package com.powernode.spring6.bean;
package com.powernode.spring6.bean;
import java.util.Date;
/**
* @author Bonbons
* @version 1.0
*/
public class Student {
// 为了演示如何注入Date类型 >> 定义一个私有日期类型的birth
private Date birth;
public void setBirth(Date birth) {
this.birth = birth;
}
@Override
public String toString() {
return "Student{" +
"birth=" + birth +
'}';
}
}
编写我们的工厂类 DateFactory.java
package com.powernode.spring6.bean;
import org.springframework.beans.factory.FactoryBean;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 使用工厂类获取我们的日期对象
* @author Bonbons
* @version 1.0
*/
public class DataFactoryBean implements FactoryBean<Date> {
// 通过构造方法传递我们要生成的日期
private final String strDate;
public DataFactoryBean(String srtDate) {
this.strDate = srtDate;
}
@Override
public Date getObject() throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 格式化我们的日期字符串
return sdf.parse(strDate);
}
@Override
public Class<?> getObjectType() {
return null;
}
}
编写我们配置文件
<bean id="date" class="com.powernode.spring6.bean.DataFactoryBean">
<!--通过它的构造方法传递我们的日期字符串-->
<constructor-arg index="0" value="2022-11-15" />
</bean>
<bean id="student2" class="com.powernode.spring6.bean.Student">
<property name="birth" ref="date" />
</bean>
- 我们通过构造注入传入指定的日期,在工厂类中通过实例方法将字符串类型的日期转化为对应的格式
- 再创建我们Student的bean的时候,将属性值注入我们工厂类获得的Bean
编写我们测试方法
@Test
public void testStudent2(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student2 = applicationContext.getBean("student2", Student.class);
System.out.println(student2);
}
可以看到我们输入的日期还是那个格式,只是通过工厂类把输入日期的格式变成了我们习惯的格式。
二、Bean的生命周期
🌔 1、什么是Bean的生命周期?
- 从对象创建到销毁的一个过程
- Spring 框架就是一个Bean的工厂,负责Bean对象的创建和销毁
🌔 2、那么一个Bean的一个完整生命周期都包括哪些部分?
- 粗略的分,可以将生命周期分为五步
- 考虑Bean后处理器,可以将生命周期分为七步
- 再考虑接口的识别,可以将生命周期分为十步
$ 分为五步的生命周期
- 都包含哪五步:
- 第一步:实例化Bean
- 第二步:Bean属性赋值
- 第三步:初始化Bean
- 第四步:使用Bean
- 第五步:销毁Bean
- 需求:需要我们自己编写初始化和销毁方法,并在配置文件中进行配置
编写我们的 User 类,我们通过User类演示Bean的这五步生命周期
package com.powernode.spring6.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
/**
* Bean的生命周期被分为五步的情况
* @author Bonbons
* @version 1.0
*/
public class User implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean {
public User(){
System.out.println("第一步,无参构造方法被调用");
}
private String name;
public void setName(String name) {
System.out.println("第二步,set方法被调用 >> 属性赋值");
this.name = name;
}
// 让我们的User类去实现这三个接口 >> 用于在执行Bean后处理器的before方法之前调用
public void initBean(){
System.out.println("第三步,初始化Bean");
}
public void destroyBean(){
System.out.println("第五步,销毁Bean");
}
}
在配置文件中声明一下我们的Bean
<!--将bean的生命周期分为五步:需要配置我们的初始化方法和销毁方法-->
<bean id="user" class="com.powernode.spring6.bean.User" init-method="initBean" destroy-method="destroyBean">
<property name="name" value="白居易" />
</bean>
- 通过 init-method 属性指定我们的初始化方法
- 通过 destroy-method 属性指定我们的销毁方法 【销毁不会自动执行,需要我们在测试方法中手动完成】
编写我们的测试方法
@Test
public void testBeanLifecycleFive(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User user = applicationContext.getBean("user", User.class);
System.out.println("第四步,使用Bean" + user);
// 需要我们手动销毁Bean,但是这个方法属于ClassPathXMLApplicationContext的,所以我们需要强转
ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
context.close();
}
- 只有正常关闭spring容器,bean的销毁方法才会被调用。
- ClassPathXmlApplicationContext类才有close()方法,所以我们需要强制类型转换
$ 分为七步的生命周期
- 我们使用了Bean后处理器,就会在初始化Bean前后添加两个方法
- 一个方法为Bean后处理器的 before 方法
- 另一个方法为Bean后处理器的after方法
- 我们这个Bean后处理器需要实现 BeanPostProcessor 接口中的方法,才能作为Bean后处理器使用
- 而且这个Bean后处理器的作用范围是整个XML文件,配置后对整个XML文件中的bean都生效
- 需求:我们写一个Bean后处理器,查看是否生命周期变成了七步
编写我们的Bean后处理器:LogBeanPostProcessor
package com.powernode.spring6.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.Nullable;
/**
* 日志Bean后处理器 >> 用来演示初始化前后插入代码的
* @author Bonbons
* @version 1.0
*/
public class LogBeanPostProcessor implements BeanPostProcessor {
// bean 我们的bean对象、beanName 我们bean的名字
@Nullable
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("执行Bean后处理器的before方法");
return bean;
}
@Nullable
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("执行Bean后处理器的after方法");
return bean;
}
}
在配置文件中配置我们的Bean后处理器
<!--配置Bean后处理器,作用范围是整个配置文件-->
<bean class="com.powernode.spring6.bean.LogBeanPostProcessor" />
测试方法我们依旧使用我们上面五步生命周期的测试方法 >> 我们可以很清楚的看到生命周期变成了七步
$ 分为十步的生命周期
- 在七步的基础上,增添了识别Bean是否实现了某些特定的接口
- 如果实现了特定的接口,就调用接口中的方法,给我们传递一些与Bean相关的一些参数信息
- 重新梳理一下,生命周期这十步都包括什么?
- 第一步,实例化Bean
- 第二步,Bean属性赋值
- 第三步,检查Bean是否实现了Aware的相关接口,并设置相关依赖
- BeanNameAware:通过setBeanName方法Spring会将Bean的名字传递给Bean
- BeanClassLoaderAware:通过setBeanClassLoader方法Spring会将加载该Bean的类加载器传递给Bean
- BeanFactoryAware :通过setBeanFactory方法Spring会将Bean工厂对象传递给Bean
- 第四步,Bean后处理器的before执行
- 第五步,检查Bean是否实现了InitializingBean接口,井调用接口方法 【afterPropertiesSet】
- 第六步,初始化Bean
- 第七步,Bean后处理器的after执行
- 第八步,使用Bean
- 第九步,检查Bean是否实现了DisposableBean接口,井调用接口方法【destroy】
- 第十步,销毁Bean
- 需求:让我们的User实现这三种接口中的方法,查看一下这十步的具体执行情况
让我们User类实现这五个接口 【第一种三个、第二种一个、第三种一个】
package com.powernode.spring6.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
/**
* Bean的生命周期被分为五步的情况
* @author Bonbons
* @version 1.0
*/
public class User implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean{
public User(){
System.out.println("第一步,无参构造方法被调用");
}
private String name;
public void setName(String name) {
System.out.println("第二步,set方法被调用 >> 属性赋值");
this.name = name;
}
// 让我们的User类去实现这三个接口 >> 用于在执行Bean后处理器的before方法之前调用
public void initBean(){
System.out.println("第三步,初始化Bean");
}
public void destroyBean(){
System.out.println("第五步,销毁Bean");
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
System.out.println("传递了类加载器" + classLoader);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("传递了创建这个Bean的工厂" + beanFactory);
}
@Override
public void setBeanName(String s) {
System.out.println("传递了Bean的名字" + s);
}
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean's destroy method");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean's afterPropertiesSet method");
}
}
配置文件、测试文件都不需要改变
$ 不同的作用域的管理方式不同
- 对于上面的生命周期,是针对我们采用默认的作用域 singleton 而言的
- 如果我们让
scope = prototype
,那么spring的工作内容只负责到使用Bean,之后的工作内容交给我们的客户端 - 需求:我们将上面十步生命周期的配置文件进行修改,将scope设置为多例模式
<!--将bean的生命周期分为五步:需要配置我们的初始化方法和销毁方法-->
<bean id="user" class="com.powernode.spring6.bean.User" init-method="initBean" destroy-method="destroyBean" scope="prototype">
<property name="name" value="白居易" />
</bean>
执行测试方法 >> Spring在完成Bean对象初始化之后,就不再追踪其生命周期了
$ 将我们自己创建的对象添加到Spring容器中
- 需求:
- 我们定义一个 Student 类,然后通过测试方法将这个类的实例添加到Spring容器中
- 通过getBean再获取一下,看是否添加成功【返回同一个对象说明我们注入成功】
编写我们的Student类
package com.powernode.spring6.bean;
/**
* @author Bonbons
* @version 1.0
*/
public class Student {
}
编写我们的测试方法:
@Test
public void testRegisterBean(){
// 我们自己创建一个对象
Student student = new Student();
System.out.println(student);
// 创建一个可以将对象加入到Bean中的工厂
DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
defaultListableBeanFactory.registerSingleton("studentBean", student);
// 通过getBean方法获取我们的Bean
Student studentBean = defaultListableBeanFactory.getBean("studentBean", Student.class);
System.out.println(studentBean);
}
通过测试结果我们可以得出结论 >> 注入我们自己new的对象成功