4. Bean 基础

192 阅读19分钟

4. Bean 基础

4.1 BeanDefinition 元信息

什么是 BeanDefinition ?

BeanDefinition 是 Spring 中定义 bean 的配置元信息接口,包含:

  • bean 的类名
  • bean 的行为,如作用域,自动绑定的模式,生命周期回调等
  • 其他 bean 引用
  • 配置设置,如 bean 属性 property

BeanDefinition 重要属性说明,具体参考AbstractBeanDefinition的属性

属性说明
beanClassBean 全类名,必须是具体类,不能是抽象类或接口
scopeBean 的作用域,如 singleton,prototype
constructorArgumentValuesBean 构造器参数,用于构造器注入
propertyValuesBean 的属性与属性值映射,用于 Setter 注入
autowireModeBean 自动绑定模式,通过 bean名称 byName,类型 byType
lazyInitBean 延迟初始化模式,延迟、非延迟
initMethodNameBean 初始化回调方法名称
destroyMethodNameBean 销毁回调方法名称
factoryBeanNameBean 工厂名称,factory-bean 指定
factoryMethodNameBean 工厂方法名称,factory-method 指定
primary
resource
dependsOn

需要注意的是并没有 bean 名称这个属性,因为注册 bean 时,key 为 bean 名称,value 为 bean 元信息 BeanDefinition,保存到 beanDefinitionMap中。

BeanDefinition 构建:1. 通过 BeanDefinitionBuilder 构建,2. 通过 AbstractBeanDefinition 派生类

  1. 通过 BeanDefinitionBuilder 构建
public static void main(String[] args) {
    // 1. 通过 BeanDefinitionBuilder 构建
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
        .genericBeanDefinition(User.class)
        .addPropertyValue("id", 1)
        .addPropertyValue("name", "小马")
        .setScope("singleton")
        .getBeanDefinition();

    System.out.println("BeanDefinitionBuilder 构造: " + beanDefinition);
}

输出结果:

BeanDefinitionBuilder 构造: Generic bean: class [org.geekbang.ioc.overview.lookup.domain.User]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null

属性是否设置成功可以 debug 查看 propertyValues 属性

  1. 通过 AbstractBeanDefinition 派生类
public static void main(String[] args) {
    // 2. 通过 AbstractBeanDefinition派生类构建bean
    GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
    // 设置 bean 类型
    genericBeanDefinition.setBeanClass(User.class);
    // 设置属性
    MutablePropertyValues propertyValues = new MutablePropertyValues();
    propertyValues.addPropertyValue("id", 1);
    propertyValues.addPropertyValue("name", "安娜");
    genericBeanDefinition.setPropertyValues(propertyValues);

    System.out.println("genericBeanDefinition 构造: " + genericBeanDefinition);
}

输出结果:

genericBeanDefinition 构造: Generic bean: class [org.geekbang.ioc.overview.lookup.domain.User]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null

4.2 Bean 的命名

每个 bean 都拥有一个或多个标识符(identifiers),这些标识符在 bean 所在的容器中必须是唯一的。通常,一个 bean 仅有一个标识符,额外的可以使用别名 Alias 来扩展。

在 xml 配置元信息中,可以用idname属性类设置 bean 的标识符。若要引入 bean 的别名,可在 name 属性使用,来间隔。

bean 的 id 或 name 属性并非必须的,如未设置,容器会为 bean 自动生成一个唯一的名称。自动生成名称由 DefaultBeanNameGenerator 来完成

使用 @Component 注解标记 bean 通常都不用设置 bean 名称,自动生成名称由 AnnotationBeanNameGenerator 来完成

  1. xml 配置 bean 不设置名称,测试能否自动生成 bean 名称
<!--  bean 不设置名称, 测试能否自动生成  -->
<bean class="org.geekbang.ioc.overview.lookup.domain.User">
    <property name="id" value="1"/>
    <property name="name" value="tracccer"/>
</bean>
// 测试 bean 自动生成的名称, xml 文件中未对 bean 设置名称
private static void testDefaultgenerate() {
    // 1. 在 xml 文件中配置 bean
    // 2. 启动spring 应用上下文
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean-name-generator.xml");
    String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
    // 打印自动生成的 bean 名称
    System.out.println("自动生成的 bean 名称: "+beanDefinitionNames[0]);
}

输出结果:

自动生成的 bean 名称: org.geekbang.ioc.overview.lookup.domain.User#0

源码分析:

DefaultBeanNameGenerator 会为未设置 bean 生成名称,使用 BeanDefinitionReaderUtils#generateBeanName() ,源码如下所示:

public static String generateBeanName(
    BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean)
    throws BeanDefinitionStoreException {

    // 获取bean的全类名
    String generatedBeanName = definition.getBeanClassName();

    String id = generatedBeanName;
    if (isInnerBean) {
        // 内部类? 类名+#+16进制数
        id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);
    }
    else {
        // 类名+#+数字, 数字为该类在容器中产生了多个bean的编号
        return uniqueBeanName(generatedBeanName, registry);
    }
    return id;
}
  1. 测试 @Component 标记的 bean 的生成名称
@Component
public class Student {
    Integer id;
    String name;
	// setter, getter
}
private static void testAnnotationGenerate() {
    // 创建并启动 applicationContext, 扫描指定包下的 @Component
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("org.geekbang.bean.definition");
    String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
    System.out.println("自动生成的 bean 名称: "+beanDefinitionNames[5]);
}

输出结果:

自动生成的 bean 名称: student

AnnotationBeanNameGenerator 会为未设置名称的 bean 自动生成名称,源码如下所示:

AnnotationBeanNameGenerator.java
    
protected String buildDefaultBeanName(BeanDefinition definition) {
    // 获取bean的全类名
    String beanClassName = definition.getBeanClassName();
    Assert.state(beanClassName != null, "No bean class name set");
    // 获取bean的类名
    String shortClassName = ClassUtils.getShortName(beanClassName);
    // 将类名转为首字母小写并返回
    return Introspector.decapitalize(shortClassName);
}
  1. @Bean 的名称,默认为方法名
@Bean  // 默认bean名称为方法名
public User getUser() {
    return new User();
}

4.3 Bean 的别名

Bean 别名 Alias 的价值:

  • 复用现有的 BeanDefinition
  • 更具有场景化的命名方法,使用第三方 jar 时使用更加合适的名称
  1. 为 bean 配置别名
<bean id="user" class="org.geekbang.ioc.overview.lookup.domain.User">
    <property name="id" value="1"/>
    <property name="name" value="tracccer"/>
</bean>

<!--  为容器中的bean "user" 设置别名 -->
<alias name="user" alias="mao-user"/>
  1. 测试从容器中通过别名获取 bean,判断是否为同一个 bean
public static void main(String[] args) {
    // 1. 在 xml 文件中配置 bean
    // 2. 启动spring 应用上下文
    BeanFactory beanFactory = new ClassPathXmlApplicationContext("bean-definition-context.xml");
    User user = beanFactory.getBean("user", User.class);
    User maoUser = beanFactory.getBean("mao-user", User.class);

    System.out.println("mao-user: " + maoUser);
    System.out.println("user == maoUser: " + (user == maoUser));
}

输出结果:

mao-user: User{id=1, name='tracccer'}
user == maoUser: true

4.4 注册 bean

注册 bean 大致有 3 种方式:

  • xml 配置元信息
    • <bean id="xx" class="com...." />
  • java 注解配置元信息
    • @Bean
    • @Component
    • @Import
  • java api 配置元信息
    • 手动设置 bean 名称:BeanDefinitionRegistry#registerBeanDefinition
    • 自动生成 bean 名称:BeanDefinitionReaderUtils#registerWithGeneratedName
    • 配置类方式:AnnotationConfigApplicationContext#register
  • 外部对象注册到容器: ConfigurableListableBeanFactory#registerSingleton
  1. @Import 注册 bean

    // 补充

  2. java api 注册bean,将 BeanDefinition 注册到 BeanDefinitionRegistry

public static void main(String[] args) {
    // 1. 创建容器
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    // 2. 注册配置类, 即代替xml配置
    applicationContext.register(BeanDefintionRegisterDemo.class);
    // 3. 启动应用上下文
    applicationContext.refresh();

    // 4.1 注册bean到容器, 手动设置bean名称
    // AnnotationConfigApplicationContext是BeanDefinitionRegistry接口的实现类
    registerUserBeanDefinition(applicationContext, "hanzo");
    User hanzo = applicationContext.getBean("hanzo", User.class);
    System.out.println("手动设置bean名称, hanzo: " + hanzo);

    // 4.2 注册bean到容器, 自动生成bean名称
    registerUserBeanDefinition(applicationContext, "");
    Map<String, User> user = applicationContext.getBeansOfType(User.class);
    // 自动生成的bean名称为 "org...User#0"
    System.out.println("自动生成bean名称, user: " + user);
}
  • 自动生成 bean 名称, 注册 bean 到容器,BeanDefinitionReaderUtils.registerWithGeneratedName()
  • 手动设置 bean 名称,注册 bean 到容器,BeanDefinitionRegistry 是 BeanDefinition 注册中心,应用上下文 AnnotationConfigApplicationContext 也实现了该接口。BeanDefinitionRegistry.registerBeanDefinition()
private static void registerUserBeanDefinition(BeanDefinitionRegistry registry, String beanName) {
    // 1. 通过 BeanDefinitionBuilder 构建 bean
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(User.class)
        .addPropertyValue("id", 1)
        .addPropertyValue("name", "半藏")
        .setScope("singleton").getBeanDefinition();

    // 2. 注册bean到容器
    if (StringUtils.isEmpty(beanName)) {
        // 自动生成bean名称, 注册bean
        BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry);
    } else {
        // 手动设置bean名称, 注册bean到注册中心(应用上下文)
        // AnnotationConfigApplicationContext应用上下文实现了BeanDefinitionRegistry接口
        registry.registerBeanDefinition(beanName, beanDefinition);
    }
}

输出结果:

手动设置bean名称, hanzo: User{id=1, name='半藏'}
自动生成bean名称, user: {hanzo=User{id=1, name='半藏'}, org.geekbang.ioc.overview.lookup.domain.User#0=User{id=1, name='半藏'}}
  1. 配置类 Configuration 方式注册 bean 到容器,即 @Configuration + @Bean 的方式,AnnotationConfigApplicationContext.register(Config.class)
public static void main(String[] args) {
    // 1. 创建容器
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    // 2. 注册配置类, 即代替xml配置
    applicationContext.register(Config.class);
    // 3. 启动应用上下文
    applicationContext.refresh();

    // 4. 从容器获取bean
    User genji = applicationContext.getBean("genji", User.class);
    System.out.println(genji);

    // 关闭应用上下文
    applicationContext.close();
}
// 配置类, 类似于xml配置文件
public static class Config {
    @Bean(name={"genji", "yuanshi"})
    public User user() {
        User user = new User();
        user.setId(1L);
        user.setName("源氏");

        return user;
    }
}

输出结果:

User{id=1, name='源氏'}
  1. 外部对象注册到容器,外部对象的生命周期并不由 Spring 容器来管理,但可以托管注册到 Spring 容器
public static void main(String[] args) {
    // 1. 创建应用上下文
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    // 2. 创建一个外部 user 对象
    User user = new User();

    // 3. 注册外部对象到容器
    ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
    beanFactory.registerSingleton("mercy", user);
    // 4. 启动应用上下文
    applicationContext.refresh();

    // 5. 依赖查找
    User mercy = applicationContext.getBean("mercy", User.class);
    // 6. 判断容器中的 bean 是否是那个外部对象
    System.out.println("mercy == user: " + (mercy == user));

    // 7. 关闭应用上下文
    applicationContext.close();
}

输出结果:

User{id=null, name='null'}
mercy == user: true

4.5 实例化 Bean

Bean 实例化 Instantiation 的 7 种方式:

  • 4 种常规方式
    • 通过构造器,配置元信息:xml,java 注解,java api
    • 通过静态工厂方法,配置元信息:xml,java api
    • 通过 bean 工厂方法,配置元信息:xml,java api
    • 通过实现 FactoryBean 接口,配置元信息:xml,java 注解,java api
  • 特殊方式
    • 通过 ServiceLoaderFactoryBean,配置元信息:xml,java 注解,java api
    • 通过 AutowireCapableBeanFactory#createBean(Class, int, boolean)
    • 通过 BeanDefinitionRegistry#registryBeanDefinition(String, BeanDefinition)

1. 4 种常规方式实例化 Bean

  1. 通过构造器实例化 bean

    // 补充

  2. 通过静态工厂方法实例化 bean,factory-method="createUser"

首先在需要实例化的 pojo 类中设置静态工厂方法,关键点是静态方法,且必须在当前类中

public class User {
    private Long id;
    private String name;

    // 静态工厂方法, 配置到xml factory-method, 通过该方法实例化 bean
    public static User createUser() {
        User user = new User();
        user.setId(3L);
        user.setName("Zarya");
        return user;
    }
}

在 xml 中配置bean,使用factory-method="createUser"指定静态工厂方法

<bean id="user-by-static-method" class="org.geekbang.ioc.overview.lookup.domain.User"
      factory-method="createUser" />
public static void main(String[] args) {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean-creation-context.xml");
    User user = applicationContext.getBean("user-by-static-method", User.class);
    System.out.println("通过静态工厂方法创建bean: " + user);
}

输出结果

通过静态工厂方法创建bean: User{id=3, name='Zarya'}
  1. 通过 bean 工厂方法实例化bean

    首先创建 bean 工厂,会通过该工厂的方法创建 bean

// bean工厂, 通过该工厂创建bean
public class DefaultUserFactory implements UserFactory{
    public User createUser() {
        User user = new User();
        user.setId(5L);
        user.setName("Echo");
        return user;
    }
}

bean 不需要配置 class,而是配置 bean 工厂factory-bean="userFactory",指定工厂方法factory-method="createUser",即上面的 java类

<!--  通过bean工厂方法实例化  factory-bean -->
<bean id="user-by-factory-bean" factory-bean="userFactory" factory-method="createUser" />

<!--  注册 bean 工厂到容器  -->
<bean id="userFactory" class="org.geekbang.bean.instantiation.DefaultUserFactory" />

从容器获取 bean 时,会自动使用 bean 中配置的 factory-bean对象,调用factory-method方法创建实例,并将其作为 bean 返回

public static void main(String[] args) {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean-creation-context.xml");

    User user2 = applicationContext.getBean("user-by-factory-bean", User.class);
    System.out.println("通过bean工厂方法创建bean: " + user2);
}

输出结果

通过bean工厂方法创建bean: User{id=5, name='Echo'}
  1. 通过实现 FactoryBean 接口实例化 bean
    1. 在 xml 中配置 FactoryBean 接口的实现类,将 bean 注册到容器
public class UserFactoryBean implements FactoryBean {
    public Object getObject() throws Exception {
        User user = new User();
        user.setId(6L);
        user.setName("Doomfist");
        return user;
    }

    public Class<?> getObjectType() {
        return User.class;
    }
}
<!--  通过FactoryBean 实例化 bean  -->
<bean id="user-by-factory-bean" class="org.geekbang.bean.instantiation.UserFactoryBean"/>

从容器获取 bean 时,当 Bean 实现了 FactoryBean 接口,会自动调用其重写的getObject()方法创建实例,并将其作为 bean 返回。如果未实现 FactoryBean 接口,自然就会调用构造方法,创建实例并返回

public static void main(String[] args) {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean-creation-context.xml");

    // xml中使用class配置FactoryBean接口的实现类
    User user3 = applicationContext.getBean("user-by-factory-bean", User.class);
    System.out.println("通过实现FactoryBean接口创建bean: " + user3);
}

输出结果:

通过实现FactoryBean接口创建bean: User{id=6, name='Doomfist'}

前面是获取工厂生产的 bean,如果要获取工厂本身,可以使用 applicationContext.getBean("&user-by-factory-bean")获取

​ 2. 使用注解 @Component 将 bean 注册到容器,代替 xml 配置

@Component("student")
public class StudentFactoryBean implements FactoryBean {

    public Object getObject() throws Exception {
        Student student = new Student();
        student.setId(2);
        student.setName("小明");
        return student;
    }

    public Class<?> getObjectType() {
        return Student.class;
    }
}
        // 注解@Component配置FactoryBean接口的实现类
        AnnotationConfigApplicationContext annotationContext = new AnnotationConfigApplicationContext("org.geekbang.bean.instantiation");
        Student student = annotationContext.getBean("student", Student.class);
        System.out.println("通过@Component + 实现FactoryBean接口创建bean:"+student);

输出结果

通过@Component + 实现FactoryBean接口创建bean:Student{id=2, name='小明'}

​ 3. 使用 java api 的方式将 bean 注册到容器

​ // 补充,参考 4.4 章节 java api 注册 bean 的方式

2. 3 种非常规方式实例化 Bean

  1. 通过 ServiceLoaderFactoryBean 实例化 bean

    5.1 首先来看一下java.util.ServiceLoader的使用,ServiceLoader 会加载META-INF/services/下配置的 java 类,文件名为接口,内容为实现类:

META-INF\services\org.geekbang.bean.instantiation.UserFactory 

org.geekbang.bean.instantiation.DefaultUserFactory
public class DefaultUserFactory implements UserFactory{
    @Override
    public User createUser() {
        User user = new User();
        user.setId(5L);
        user.setName("Echo");
        return user;
    }
}

使用ServiceLoader.load(UserFactory.class)加载 DefaultUserFactory 类并创建实例,调用createUser()方法创建 User 实例并输出

public static void serviceLoaderDemo() {
    // 使用ServiceLoader加载 META-INF/services/ 下配置的 UserFactory 实现类
    ServiceLoader<UserFactory> serviceLoader = ServiceLoader.load(UserFactory.class);

    // 遍历加载到的 UserFactory 实现类
    for (UserFactory userFactory : serviceLoader) {
        System.out.println("ServiceLoader加载的类:" + userFactory.createUser());
    }
}

输出结果:

ServiceLoader加载的类:User{id=5, name='Echo'}

​ 5.2 再来看一下 ServiceLoaderFactoryBean,是 FactoryBean 接口的实现类,用于实例化 ServiceLoader,即和第 4 种实例化 bean 的方式一致,不过这个 FactoryBean 接口的实现类是由 Spring 提供,实现了getObject()getObjectType()方法,源码如下所示:

ServiceLoaderFactoryBean.java

public class ServiceLoaderFactoryBean extends AbstractServiceLoaderBasedFactoryBean implements BeanClassLoaderAware {
	// 继承自AbstractFactoryBean
    // 创建 bean
    @Override
	public final T getObject() throws Exception {
        // 单例
		if (isSingleton()) {
			return (this.initialized ? this.singletonInstance : getEarlySingletonInstance());
		}
		else {
            // 非单例, 创建ServiceLoader实例
			return createInstance();
		}
	}
    
    // 使用ServiceLoader加载指定类, 返回ServiceLoader
    // 继承自AbstractServiceLoaderBasedFactoryBean
    @Override
	protected Object createInstance() {
		Assert.notNull(getServiceType(), "Property 'serviceType' is required");
        
        // 通过ServiceLoader加载 /META-INF/services 下配置的类
        // 这里使用beanClassLoader类加载器, 会加载到容器
		return getObjectToExpose(ServiceLoader.load(getServiceType(), this.beanClassLoader));
	}
    
	@Override
	protected Object getObjectToExpose(ServiceLoader<?> serviceLoader) {
		return serviceLoader;
	}
    // 返回serviceType, 即加载的类的类型, 在xml中配置, 这里是UserFactory
    public Class<?> getServiceType() {
		return this.serviceType;
	}
        
    // 实现FactoryBean接口
	@Override
	public Class<?> getObjectType() {
		return ServiceLoader.class;
	}
}

5.3 使用 ServiceLoaderFactoryBean 加载META-INF/services/下配置的 java 类

配置需要加载并实例化的 Java 类到下面文件,文件名为接口,内容为实现类

META-INF\services\org.geekbang.bean.instantiation.UserFactory 

org.geekbang.bean.instantiation.DefaultUserFactory

xml 中配置 ServiceLoaderFactoryBean,会将该 ServiceLoader 注册到容器并实例化,在实例化过程中会

<!--  配置 ServiceLoaderFactoryBean  -->
<bean id="userFacotoryServiceLoader" class="org.springframework.beans.factory.serviceloader.ServiceLoaderFactoryBean">
    <property name="serviceType" value="org.geekbang.bean.instantiation.UserFactory"/>
</bean>

从容器中获取配置的 ServiceLoade bean,然后遍历 ServiceLoader,得到DefaultUserFactory实例,调用createUser()方法创建 User 实例并输出

public static void main(String[] args) {
    BeanFactory beanFactory = new ClassPathXmlApplicationContext("bean-instantiation-special-context.xml");
    // xml中配置了 bean ServiceLoaderFactoryBean, 获取 ServiceLoader 实例
    ServiceLoader<UserFactory> serviceLoader = beanFactory.getBean("userFacotoryServiceLoader", ServiceLoader.class);

    // 遍历 ServiceLoader 实例
    for (UserFactory userFactory : serviceLoader) {
        System.out.println("ServiceLoaderFactoryBean加载实例化的类:" + userFactory);
        System.out.println("使用bean工厂UserFactory创建user:" + userFactory.createUser());
    }

输出结果:

ServiceLoaderFactoryBean加载实例化的类:org.geekbang.bean.instantiation.DefaultUserFactory@662ac478
使用bean工厂UserFactory创建userUser{id=5, name='Echo'}

// 补充:这个真的算是实例化bean吗?user 并没有被注册到spring容器,只有 userFacotoryServiceLoader bean 被注册到 spring 容器,这里的例子可能并不合适,应该配置 serviceType 为 User?

  1. 通过 AutowireCapableBeanFactory 实例化 Bean
// 2. 通过 AutowireCapableBeanFactory 实例化 bean
AutowireCapableBeanFactory autowireCapableBeanFactory = applicationContext.getAutowireCapableBeanFactory();
User user = autowireCapableBeanFactory.createBean(User.class);
System.out.println(user);

UserFactory userFactory = autowireCapableBeanFactory.createBean(DefaultUserFactory.class);
System.out.println(userFactory.createUser());

输出结果:

User{id=null, name='null'}
User{id=5, name='Echo'}
  1. 通过 BeanDefinitionRegistry 实例化 Bean

    // 补充: 参考 4.4.2 注册Bean的时候也会实例化Bean

4.6 初始化 Bean

Bean 初始化 Initialization 后设置回调方法的三种方式:

  • @PostConstruct 标记方法初始化后调用
  • 实现 InitializingBean 接口的 afterPropertiesSet() 方法
  • 自定义初始化方法,都是将初始化方法名保存到 BeanDefinition 元信息的属性 initMethodName 中
    • xml 配置:<bean init-method="xxx" />
    • Java 注解:@Bean(initMethod="xxx")
    • Java API:AbstractBeanDefinition#setInitMethodName(String)

**调用时机:**在应用上下文启动创建 bean,构造方法调用完成后回调

  1. 通过 @PostConstruct 标记初始化方法,实现 InitializingBean 接口设置初始化方法
/**
 * 设置 bean 初始化方法
 * 1. @PostConstruct 标记方法
 * 2. 实现InitializingBean#afterPropertiesSet方法
 * 3. 使用@Bean(initMethod="")指定初始化方法
 */
public class Teacher implements InitializingBean {
    // @PostConstruct 标记初始化方法
    @PostConstruct
    public void init() {
        System.out.println("@PostConstruct设置初始化方法: Teacher 初始化中...");
    }

    // 实现InitializingBean#afterPropertiesSet方法
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("实现InitializingBean接口设置初始化方法: Teacher 初始化中...");
    }

    // 使用@Bean(initMethod="")标记该方法为初始化方法
    public void initTeacher() {
        System.out.println("initMethod设置初始化方法: Teacher 初始化中...");
    }
}

注册 bean 到容器,并使用 @Bean(initMethod = "xxx") 指定初始化方法,容器会在启动时创建 bean,并调用初始化方法。

public class BeanInitializationDemo {
    public static void main(String[] args) {
        // 1.创建并启动 ApplicationContext 容器, 使用注解配置
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanInitializationDemo.class);
        // 2.依赖查找, 根据类型
        Teacher teacher = applicationContext.getBean(Teacher.class);
    }

    // 指定初始化方法, 与xml方式作用一直
    @Bean(initMethod = "initTeacher")
    public Teacher createTeacher() {
        return new Teacher();
    }
}

上面使用注解的方式注册 bean 到容器,这里再补充一下 xml 方式

<bean id="teacher" class="com...Teacher" init-method="initTeacher" />

输出结果:

@PostConstruct设置初始化方法: Teacher 初始化中...
实现InitializingBean接口设置初始化方法: Teacher 初始化中...
initMethod设置初始化方法: Teacher 初始化中...

通过输出结果我们也可以知道,三种初始化方法的调用顺序是:

​ @PostConstruct → InitializingBean接口 → initMethod,会在构造方法调用完成后调用。

  1. 通过 Java Api 的方式指定初始化方法,BeanDefinitionBuilder.setInitMethodName("initTeacher")底层是AbstractBeanDefinition#setInitMethodName(String)。需要注意的是,自定义初始化的 3 种方式,都是将初始化方法名保存到 BeanDefinition 元信息的属性 initMethodName 中
public static void main(String[] args) {
    // 1.创建并启动 ApplicationContext 容器, 使用注解配置
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanInitializationDemo2.class);
    // 2. 创建BeanDefinition元信息, 并设置initMethod
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
        .genericBeanDefinition(Teacher.class)
        .setInitMethodName("initTeacher")
        .getBeanDefinition();
    // 3. 注册bean到容器, 设置bean名称为Winston
    applicationContext.registerBeanDefinition("Winston", beanDefinition);

    // 4. 查找bean, 此时会创建bean实例并调用初始化方法
    applicationContext.getBean("Winston");
}

输出结果:

@PostConstruct设置初始化方法: Teacher 初始化中...
实现InitializingBean接口设置初始化方法: Teacher 初始化中...
initMethod设置初始化方法: Teacher 初始化中...		# 这一步是通过java api指定

实例化 Bean 与初始化 Bean 的区别?

源码分析,在应用上下文启动 refresh() 的源码中,这行代码是实例化非延迟加载的 bean的。

finishBeanFactoryInitialization(beanFactory);

4.7 延迟初始化 Bean

@Lazy 注解的主要作用主要是减少 SpringIOC 容器启动的加载时间。当出现循环依赖时,也可以添加@Lazy 解决。

Bean 延迟初始化 Lazy-Initialization 有两种方式:

  • xml 配置:<bean lazy-init="true" />
  • Java 注解:@Lazy

需要注意,延迟初始化并不是延迟注册,无论是否设置了延迟初始化,都会在应用上下文启动时注册 BeanDefinition 元信息到容器。

  1. Java 注解的方式设置 Bean 的延迟加载
public class BeanLazyInitializationDemo {
    public static void main(String[] args) {
        // 1.创建并启动应用上下文, 使用注解配置, 不需要手动refresh启动
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanLazyInitializationDemo.class);

        // 非延迟加载会在应用上下文启动时初始化bean
        System.out.println("========Spring应用上下文已启动=======");
        // 延迟加载会在获取bean时初始化bean, 但已经注册BeanDefinition到容器了
        applicationContext.getBean(Teacher.class);
    }
    
    @Lazy	// 标记延迟加载,可以用在类,方法等
    @Bean
    public Teacher createTeacher() {
        return new Teacher();
    }
}

输出结果:

========Spring应用上下文已启动=======
@PostConstruct设置初始化方法: Teacher 初始化中...
实现InitializingBean接口设置初始化方法: Teacher 初始化中...

注释掉 @Lazy 输出结果:

@PostConstruct设置初始化方法: Teacher 初始化中...
实现InitializingBean接口设置初始化方法: Teacher 初始化中...
========Spring应用上下文已启动=======
  1. xml 方式设置 bean 延迟加载
<bean id="teacher" class="com...Teacher" lazy-init="true" />

4.8 销毁 Bean

所谓的销毁 Bean 就是将 Bean 从容器中移除,并不是 GC,这点需要注意。

设置 Bean 在销毁 Destroy 前回调方法的三种方式:

  • @PreDestroy 标记方法
  • 实现 DisposableBean 接口的 destroy() 方法
  • 自定义销毁方法
    • xml 配置:<bean destroy="xxx" />
    • java 注解:@Bean(destroy="xxx")
    • java api:AbstractBeanDefinition#setInitMethodName(String)

可以看到,这些方式都与 4.7 初始化 Bean 一一对应。

调用时机: 应用上下文关闭之前,

  1. 通过 @PreDestroy 标记销毁回调方法,实现 DisposableBean 接口设置销毁回调方法
public class Teacher implements InitializingBean, DisposableBean {
    public Teacher() {
        System.out.println("Teacher 构造函数....");
    }

	// 省略初始化后回调方法

    @PreDestroy
    public void preDestroy() {
        System.out.println("@PreDestroy设置销毁方法: Teacher 销毁中...");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("实现DisposableBean接口设置销毁方法: Teacher 销毁中...");
    }

    // 使用@Bean(destroyMethod="")标记该方法在销毁前调用
    public void destroyTeacher() {
        System.out.println("destroyMethod设置销毁方法: Teacher 销毁中...");
    }
}
public class BeanDestroyDemo {
    public static void main(String[] args) {
        // 1.创建注解配置的应用上下文
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(BeanDestroyDemo.class);

        System.out.println("=========Spring应用上下文开始启动=========");
        // 2. 启动应用上下文
        applicationContext.refresh();
        System.out.println("=========Spring应用上下文已启动=========");

        // 3.依赖查找, 根据类型
        applicationContext.getBean(Teacher.class);
        // 4. 关闭应用上下文
        System.out.println("=========Spring应用上下文开始关闭=========");
        applicationContext.close();
        System.out.println("=========Spring应用上下文已关闭=========");
    }

    // 指定初始化方法, 与xml方式作用一直
    @Bean(destroyMethod = "destroyTeacher")
    public Teacher createTeacher() {
        return new Teacher();
    }
}

输出结果:

=========Spring应用上下文开始启动=========
Teacher 构造函数....
@PostConstruct设置初始化方法: Teacher 初始化中...
实现InitializingBean接口设置初始化方法: Teacher 初始化中...
=========Spring应用上下文已启动=========
=========Spring应用上下文开始关闭=========
@PreDestroy设置销毁方法: Teacher 销毁中...
实现DisposableBean接口设置销毁方法: Teacher 销毁中...
destroyMethod设置销毁方法: Teacher 销毁中...
=========Spring应用上下文已关闭=========

通过输出结果我们也可以知道,三种初始化方法的调用顺序是:@PreDestroy,DisposableBean接口,destroyMethod,会在 bean 销毁前调用。

源码分析,在应用上下文关闭 close() 的源码中,最终会调用下面这行代码来销毁 Bean 并回调方法。

AbstractApplicationContext.java
protected void destroyBeans() {
    getBeanFactory().destroySingletons();
}

DefaultSingletonBeanRegistry.java
public void destroySingleton(String beanName) {
    removeSingleton(beanName);

    DisposableBean disposableBean;
    // 将实现了DisposableBean接口的beanName删除
    synchronized (this.disposableBeans) {
        disposableBean = (DisposableBean) this.disposableBeans.remove(beanName);
    }
    
    // 回调实现的DisposableBean#destroy 方法
    destroyBean(beanName, disposableBean);
}
  1. 通过 xml 方式或 java api 方式设置销毁前回调方法,参考 4.6 章节,与之对应
<bean id="teacher" class="com...Teacher" init-method="destroyTeacher" />
public static void main(String[] args) {
    // 1.创建并启动 ApplicationContext 容器
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanDestroyDemo2.class);
    // 2. 创建 BeanDefinition 元信息, 并设置 DestroyMethod
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
        .genericBeanDefinition(Teacher.class)
        .setDestroyMethodName("destroyTeacher")
        .getBeanDefinition();
    // 3. 注册 bean 到容器, 设置 bean 名称为Winston
    applicationContext.registerBeanDefinition("Winston", beanDefinition);
    applicationContext.getBean("Winston");

    // 4. 关闭容器,会回调DestroyMethod
    applicationContext.close();
}

4.8 垃圾回收 Bean

Bean 垃圾回收 GC 的 3 个步骤:

  1. 关闭 Spring 容器(应用上下文)
  2. 执行 GC
  3. Spring Bean 重写的 finalize() 方法被调用
public static void main(String[] args) {
    // 1.创建注解配置的应用上下文
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    applicationContext.register(BeanDestroyDemo.class);

    System.out.println("=========Spring应用上下文开始启动=========");
    // 2. 启动应用上下文
    applicationContext.refresh();
    System.out.println("=========Spring应用上下文已启动=========");

    // 3.依赖查找, 根据类型
    applicationContext.getBean(Teacher.class);
    // 4. 关闭应用上下文
    System.out.println("=========Spring应用上下文开始关闭=========");
    applicationContext.close();
    System.out.println("=========Spring应用上下文已关闭=========");

    // 强制进行 GC
    System.gc();
}
public class Teacher implements InitializingBean, DisposableBean {
    public Teacher() {
        System.out.println("Teacher 构造函数....");
    }

    // 会在垃圾回收前被调用
    @Override
    protected void finalize() throws Throwable {
        System.out.println("Teacher 析构函数, 开始GC....");
    }
    // 省略初始化和销毁的回调方法
}

输出结果:

=========Spring应用上下文开始启动=========
Teacher 构造函数....
@PostConstruct设置初始化方法: Teacher 初始化中...
实现InitializingBean接口设置初始化方法: Teacher 初始化中...
=========Spring应用上下文已启动=========
=========Spring应用上下文开始关闭=========
@PreDestroy设置销毁方法: Teacher 销毁中...
实现DisposableBean接口设置销毁方法: Teacher 销毁中...
destroyMethod设置销毁方法: Teacher 销毁中...
=========Spring应用上下文已关闭=========
Teacher 析构函数, 开始GC....

通过以上可以知道,bean 想要被垃圾回收,首先得关闭容器,触发 GC,才会被回收。n 年前初学 spring 时纳闷销毁 bean 到底是不是垃圾回收,bean 什么时候会被销毁,当时可能应该多问问。

4.9 面试题

  1. 如何注册一个 Spring Bean?

    答:通过 BeanDefinition

  2. 什么是 Spring BeanDefinition?

    答:回顾 4.1 BeanDefinition 元信息,BeanDefinition 就是 Bean 的描述信息,包括 id,class,scope,属性与属性值,lazy,初始化回调方法,销毁回调方法等信息,可以通过 BeanDefinitionBuilder 创建 BeanDefinition,可以将 BeanDefinition 注册到容器,所有 bean 的元信息都以 < beanName-> BeanDefinition> 的形式保存在 BeanFactory 的 beanDefinitionMap 属性中。

    beanDefinitionMap 的类型是 ConcurrentHashMap,可以引到并发问题上。

  3. Spring 容器是怎么管理注册 Bean 的?

    答:参考后续章节,IOC 配置元信息读取和解析,依赖查找和注入,Bean 的生命周期

  4. Spring 中控制Bean生命周期的三种方式

init-method & destroy-method@PostConstruct & @PreDestroyInitializingBean & DisposableBean
执行顺序最后最先中间
组件耦合度无侵入(只在 <bean>@Bean 中使用)与 JSR 规范耦合与 SpringFramework 耦合
容器支持xml 、注解原生支持注解原生支持,xml需开启注解驱动xml 、注解原生支持
单实例Bean
原型Bean只支持 init-method