【重写SpringFramework】第一章beans模块:Bean的初始化(chapter 1-8)

1.前言

前边我们介绍了创建实例和填充对象的流程,这是整个创建流程最重要的工作。有时候用户需要对 Bean 进行自定义的操作,这一过程称为初始化。此外,还有一些比较特殊的对象,本身管理着一定的资源,当对象销毁时需要释放这些资源,因此我们还需要相应的销毁操作。初始化和销毁操作统称为 Bean 的生命周期,这些操作的自主程度很高,具体实现取决于开发人员的需求。本节主要讨论 Bean 的初始化,至于销毁工作将在下一节介绍。

2. 初始化方式

2.1 概述

Spring 提供了三种方式来处理初始化操作,简单介绍如下:

  1. 实现 InitlizeBean 接口
  2. 在方法上声明 @PostConstruct 注解,该注解是由 JDK 提供的
  3. 设置 AbstractBeanDefinition 类的 initMethodName 属性

从执行顺序上来说,注解 > 接口 > 自定义方法。从适用情况上来说,三种方式有细微的差别。接口主要用于 Spring 框架内部,继承情况一目了然,方便管理。注解主要是用户使用,看重的是便捷性。自定义方法主要用于整合第三方框架的组件,一般来说,这些组件不会使用 Spring 提供的接口或 JDK 的注解,因此需要指定具体的初始化或销毁方法。

2.2 InitializingBean

InitializingBean 接口定义了初始化的方法,任何实现该接口的对象都将执行 afterPropertiesSet 方法。这种情况不需要额外的组件来处理,只需要使用 instanceof 关键字进行判断即可。

public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

2.3 注解声明

@PostConstruct 注解仅起到标识的作用,具体工作是由 InitDestroyAnnotationBeanPostProcessor 组件完成的。

public class Foo {

    @PostConstruct
    public void init() {}
}

BeanPostProcessor 接口本身定义了初始化操作DestructionAwareBeanPostProcessor 作为子接口定义销毁操作。本节不介绍该接口,仅作为继承结构中的一环。InitDestroyAnnotationBeanPostProcessor 实现了 DestructionAwareBeanPostProcessor 接口,同时拥有初始化和销毁操作的功能,负责处理注解声明的方法。其中initAnnotationType 字段表示初始化方法的注解,destroyAnnotationType 字段表示销毁方法的注解,默认的构造方法指定了注解类。

public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareBeanPostProcessor, PriorityOrdered {
    private Class<? extends Annotation> initAnnotationType;
    private Class<? extends Annotation> destroyAnnotationType;
    private transient final Map<Class<?>, LifecycleMetadata> lifecycleMetadataCache = new ConcurrentHashMap<>(256);

    public InitDestroyAnnotationBeanPostProcessor() {
        this.initAnnotationType = PostConstruct.class;
        this.destroyAnnotationType = PreDestroy.class;
    }
}

该类的逻辑与 AutowiredAnnotationBeanPostProcessor 类似,都是通过元数据进行处理。内部类 LifecycleMetadata 表示一个类的初始化和销毁信息,LifecycleElement 表示一个初始化或销毁方法。

private class LifecycleMetadata{
    private final Collection<LifecycleElement> initMethods;     //init方法集合
    private final Collection<LifecycleElement> destroyMethods;  //destroy方法集合
}

private static class LifecycleElement {
    private final Method method;
}

2.4 自定义初始化方法

一般来说,Spring 对于同一功能提供两种解决方案。一种是编程式的,另一种是声明式的,正如 InitializingBean 接口和 @PostConstruct 注解。那么什么情况下需要使用自定义的初始化方法?实际上 Spring 内部用于处理特殊情况,比如 XML 文件中 bean 标签的 init-method 属性,以及 @Bean 注解的 initMethod 属性。

如下所示,在 Spring 的配置文件中,使用 bean 标签来加载对象,此时可以通过 init-method 属性指定初始化方法。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="foo" class="cn.stimd.spring.Foo" init-method="customInit"></bean>
</beans>

还有就是配置类中的工厂方法,声明 @Bean 注解并指定 initMethod 属性。工厂方法的作用与 Spring 配置文件中的 bean 标签是一样的,表现形式不同而已。

@Configuration
public class XxxConfig {

    @Bean(initMethod="customInit")
    public Foo foo() {
        return new Foo();
    }
}

注:本教程不实现 XML 方式的任何功能,一律使用声明注解的方式来代替。因此,我们不介绍 Spring 配置文件的具体实现,至于配置类的内容将在第三章 context 模块进行介绍。

3.初始化流程

3.1 概述

回到 AbstractAutowireCapableBeanFactorydoCreateBean 方法,第四步执行了初始化流程。

//所属类[cn.stimd.spring.beans.factory.support.AbstractAutowireCapableBeanFactory]
//创建对象
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
    //1. Bean的实例化(略)
    //2. 提前暴露Bean解决循环依赖(TODO)
    //3. 属性填充(略)

    //4. 初始化
    Object exposedObject = initializeBean(beanName, exposedObject, mbd);
    //5. 注册需要destroy方法的Bean(TODO)
    return exposedObject;
}

initializeBean 方法一共分为四步,简单介绍如下:

  1. 处理感知接口
  2. 通过 BeanPostProcessor 完成初始化前的处理
  3. 处理实现了 InitlizeBean 接口的对象
  4. 通过 BeanPostProcessor 完成初始化后的处理
//所属类[cn.stimd.spring.beans.factory.support.AbstractAutowireCapableBeanFactory]
//初始化
protected Object initializeBean(String beanName, final Object bean, RootBeanDefinition mbd) {
    //1. 调用Aware接口
    invokeAwareMethods(bean);

    //2. 调用PostProcessor在Bean初始化之前进行处理(比如InitDestroyAnnotationBeanPostProcessor处理使用@PostConstruct的初始化方法)
    Object wrappedBean = bean;
    if (!mbd.isSynthetic()) {
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }

    //3. 调用初始化方法
    invokeInitMethods(wrappedBean, mbd);

    //4. 调用PostProcessor在Bean初始化之后进行处理(比如AbstractAutoProxyCreator创建代理对象)
    if (!mbd.isSynthetic()) {
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }
    return wrappedBean;
}

3.2 Aware 感知接口

Aware 是一个标记接口,本身没有定义任何方法。Aware 接口的作用是标记一个类对指定类型感兴趣,Spring 容器将对应的组件赋给目标对象。感知接口注入的组件一般是 Spring 框架内置的,比如 BeanFactoryAware 负责注入 BeanFactory。后续我们还会讲到 ApplicationContextResourceLoaderEnviromentServletContext 等组件以及对应的感知接口,它们都是 Spring 框架自带的。

//标记接口
public interface Aware {}

public interface BeanFactoryAware extends Aware {
    //注入BeanFactory
    void setBeanFactory(BeanFactory beanFactory);
}

回到 Bean 的初始化流程,invokeAwareMethods 方法的实现较为简单,判断实例是否实现了某个感知接口,然后调用对应的回调方法即可。

//所属类[cn.stimd.spring.beans.factory.support.AbstractAutowireCapableBeanFactory]
//回调感知接口
private void invokeAwareMethods(Object bean) {
    if(bean instanceof Aware){
        //注入BeanFactory
        if(bean instanceof BeanFactoryAware){
            ((BeanFactoryAware) bean).setBeanFactory(this);
        }
    }
}

3.3 初始化前操作

applyBeanPostProcessorsBeforeInitialization 方法负责初始化之前的一些操作,调用 BeanPostProcessor 接口的 postProcessBeforeInitialization 方法。典型应用是InitDestroyAnnotationBeanPostProcessor 组件处理声明了 @PostConstruct 注解的初始化方法。

//所属类[cn.stimd.spring.beans.factory.support.AbstractAutowireCapableBeanFactory]
private Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) {
    Object result = existingBean;
    for (BeanPostProcessor processor : getBeanPostProcessors()) {
        //执行初始化前的操作
        Object current = processor.postProcessBeforeInitialization(result, beanName);
        if(current == null){
            return result;
        }
        result = current;
    }
    return result;
}

InitDestroyAnnotationBeanPostProcessor 的处理过程与 AutowiredAnnotationBeanPostProcessor 类似,这里省略了具体的实现过程,详情参考代码。大致的逻辑是,以反射的方式遍历当前对象,寻找声明了 @PostConstruct@PreDestroy 注解的方法,并添加到元数据的缓存中,最后通过反射的方式调用所有的初始化方法。

3.4 初始化操作

invokeInitMethods 方法定义了两种初始化的方式。一是实现 InitializingBean 接口,并调用 afterPropertiesSet 回调方法。二是自定义的初始化方法,方法名保存在 AbstractBeanDefinitioninitMethodName 属性中。

//所属类[cn.stimd.spring.beans.factory.support.AbstractAutowireCapableBeanFactory]
private void invokeInitMethods(final Object bean, RootBeanDefinition mbd) throws Exception {
    //如果是InitializingBean,调用初始化方法(主要是框架内部使用,检查各项属性是否设置成功,否则抛出异常,中止容器的创建过程)
    if(bean instanceof InitializingBean){
        ((InitializingBean) bean).afterPropertiesSet();
    }

    //调用自定义初始化方法,比如Xml或@Bean的initMethod属性指定的方法
    if(mbd != null){
        String initMethodName = mbd.getInitMethodName();
        if(initMethodName != null && !"afterPropertiesSet".equals(initMethodName)){
            Method initMethod = BeanUtils.findMethod(bean.getClass(), initMethodName);
            if(initMethod != null){
                ReflectionUtils.makeAccessible(initMethod);
                initMethod.invoke(bean);
            }
        }
    }
}

3.5 初始化后操作

BeanPostProcessor 接口的 postProcessAfterInitialization 方法用于在 Bean 初始化之后的一些操作,最典型的应用是创建代理对象。此时实例已经创建完毕,依赖注入和初始化的工作也都完成了,可以说是万事俱备,正是创建代理对象的最佳时机。(有关代理的部分将在第二章 aop 模块中介绍,此处不深入讨论)

//所属类[cn.stimd.spring.beans.factory.support.AbstractAutowireCapableBeanFactory]
private Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) {
    Object result = existingBean;
    for (BeanPostProcessor processor : getBeanPostProcessors()) {
        //执行初始化后的操作
        Object current = processor.postProcessAfterInitialization(result, beanName);
        if(current == null){
            return result;
        }
        result = current;
    }
    return result;
}

4. 扩展

4.1 所有单例实例化

初始化的操作都是在 getBean 方法中执行的,getBean 方法的使用场景有两个,一是在依赖注入时由容器自动调用,二是用户在代码中手动调用。现在的问题是,如何保证所有的单例都被创建?要知道并不是所有的单例都是依赖项,也不能指望由用户完成这一工作,因此 Spring 框架必须提供一个解决方案。ConfigurableBeanFactory 接口定义了 preInstantiateSingletons 方法,用于实例化所有的单例 Bean。

public interface ConfigurableBeanFactory  {
    void preInstantiateSingletons();
}

DefaultListableBeanFactory 实现了该方法。所有的单例在创建之前都以 BeanDefinition 的形式缓存起来了,因此只需要遍历 beanDefinitionNames,依次调用 getBean 方法即可。

public class DefaultListableBeanFactory {
    //BeanDefinition名称
    private volatile List<String> beanDefinitionNames = new ArrayList<>(256);

    @Override
    public void preInstantiateSingletons() {
        //实例化所有非懒加载的单例Bean
        List<String> beanNames = new ArrayList<>(beanDefinitionNames);
        for(String beanName : beanNames){
            RootBeanDefinition bd = getMergedBeanDefinition(beanName);
            if(bd.isSingleton() && !bd.isLazyInit()){
                getBean(beanName);
            }
        }

        //所有单例Bean实例化后,触发SmartInitializingSingleton回调
        for (String beanName : beanNames) {
            Object singletonInstance = getSingleton(beanName);
            if (singletonInstance instanceof SmartInitializingSingleton) {
                ((SmartInitializingSingleton) singletonInstance).afterSingletonsInstantiated();
            }
        }
    }
}

4.2 SmartInitializingSingleton 接口

preInstantiateSingletons 方法在确保所有的单例实例化之后,还执行了 SmartInitializingSingleton 接口的相关操作。SmartInitializingSingleton 接口的作用是什么?我们先来看源码中的定义:

This interface can be implemented by singleton beans in order to perform some initialization after the regular singleton instantiation algorithm, avoiding side effects with accidental early initialization.

该接口可以被单例实现,在常规的单例实例化流程结束之后进行一些操作,避免了提前初始化可能产生的副作用。

当我们在初始化时进行某些操作时,并不能保证所有的单例都已经创建完成了。如果调用了 getBeansOfType 这种涉及全部单例的方法,可能会使得某些单例提前实例化,从而导致不可预知的结果,这便是所谓的副作用。如果需要对所有单例进行操作,可以实现 SmartInitializingSingleton 接口。此时所有的单例都已创建,也不会产生副作用了。我们在上一节介绍依赖注入时,分析了为什么要避免提前实例化,可见 Spring 在原则性问题上保持一贯的立场。

5. 测试

5.1 初始化操作

测试类 MyInitBean 实现了三种初始化方式,init 方法声明注解,afterPropertiesSet 方法实现接口,customInit 是自定义的初始化方法。

//测试类:实现了三种方式的初始化
public class MyInitBean implements InitializingBean {

    @PostConstruct
    private void init(){
        System.out.println("初始化 PostConstruct...");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("初始化 afterPropertiesSet");
    }

    public void customInit(){
        System.out.println("初始化 customInit...");
    }
}

测试方法比较简单,需要注意三点。一是向 BeanFactory 添加 InitDestroyAnnotationBeanPostProcessor 组件来处理 @PostConstruct 注解。二是创建 RootBeanDefinition 对象后,调用 setInitMethodName 方法指定初始化方法的名称。三是调用 preInstantiateSingletons 方法确保所有单例都被创建,取代了调用 getBean 方法。

//测试方法
@Test
public void testInitMethod(){
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    //添加InitDestroyAnnotationBeanPostProcessor来处理@PostConstruct注解
    factory.addBeanPostProcessor(new InitDestroyAnnotationBeanPostProcessor());

    RootBeanDefinition definition = new RootBeanDefinition(MyInitBean.class);
    //设置自定义初始化方法
    definition.setInitMethodName("customInit");
    factory.registerBeanDefinition("initBean", definition);

    //确保所有单例实例化
    factory.preInstantiateSingletons();
}

从测试结果中可以看到,不同初始化方式的执行顺序。先是注解声明的方法,然后是接口方法,最后是自定义的初始化方法。

初始化 @PostConstruct 注解方法...
初始化 InitializingBean 接口方法...
初始化自定义 init 方法...

5.2 代理对象

测试类 SimpleProxy 实现了 InvocationHandler 接口,说明这是 JDK 动态代理。target 字段表示目标对象。getProxy 方法的作用是创建 JDK 代理对象。invoke 方法在执行代理对象的任何方法时均会触发,这里打印日志模拟拦截操作,然后调用目标对象的方法。

//测试类:动态代理简单实现
public class SimpleProxy implements InvocationHandler {
    private Object target;

    public SimpleProxy(Object target) {
        this.target = target;
    }

    //创建JDK代理对象
    public Object getProxy() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //模拟拦截逻辑
        System.out.println("代理拦截:目标对象 [" + this.target.getClass().getName() + "], 方法名 [" + method.getName() + "]");
        //调用目标对象的方法
        return method.invoke(target, args);
    }
}

测试类 SimpleProxyPostProcessor 实现了 InstantiationAwareBeanPostProcessor 接口,postProcessAfterInitialization 方法的作用是在对象初始化后创建代理对象。

//测试类:创建SimpleProxy代理对象
public class SimpleProxyPostProcessor implements InstantiationAwareBeanPostProcessor {
    private Set<String> cache = new HashSet<>();

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (!this.cache.contains(beanName)) {
            System.out.println("初始化后创建代理对象");
            //返回代理对象,而非原始对象
            return createProxy(bean);
        }
        return bean;
    }

    private Object createProxy(Object bean) {
        if (bean instanceof ITarget) {
            return new SimpleProxy(bean).getProxy();
        }
        return bean;
    }
}

由于我们使用的是 JDK 动态代理,需要定义一个接口,然后让测试类 TargetBean 实现该接口。

//测试类:用于创建代理的接口
public interface ITarget {
    void handle();
}

//测试类:用于创建代理的实现类
public class TargetBean implements ITarget{

    public void handle() {
        System.out.println("执行handle方法...");
    }
}

在测试方法中,需要注意两点。其一,将 SimpleProxyPostProcessor 组件注册到 Spring 容器中。其二,获取对象是必须是接口类型,如果指定 TargetBean 将报错,这是因为此时的 Bean 是一个代理。

//测试方法
@Test
public void testCreateProxy() {
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    factory.addBeanPostProcessor(new SimpleProxyPostProcessor());
    factory.registerBeanDefinition("target", new RootBeanDefinition(TargetBean.class));

    ITarget target = factory.getBean("target", ITarget.class);
    target.handle();
    System.out.println("Bean的实际类型:" + target.getClass().getName());
}

从测试结果可以看到,在执行 handle 方法时,先执行了 SimpleProxy 的拦截逻辑。对象的实际类型为 com.sun.proxy.$Proxy5,说明代理对象创建成功。

初始化后创建代理对象[target]
代理拦截:目标对象 [beans.lifecycle.TargetBean], 方法名 [handle]
执行handle方法...
Bean的实际类型:com.sun.proxy.$Proxy5

6. 总结

本节介绍了 Bean 的初始化流程,包括三种实现方式,分别是实现接口、声明注解、以及指定自定义方法。这三种方式没有本质区别,只是在调用顺序上有先后之分。整个初始化流程分为四个部分,如下所示:

  • 感知接口注入:回调 Aware 接口的相关方法,为当前对象注入感兴趣的组件。
  • 初始化前操作:回调 BeanPostProcessor 接口的 postProcessBeforeInitialization 方法,比如处理声明了 @PostConstruct 注解的初始化方法。
  • 初始化操作:包括两方面,一是调用 InitializingBean 接口的初始化方法,二是调用自定义的初始化方法。
  • 初始化后操作:回调 BeanPostProcessor 接口的 postProcessAfterInitialization 方法,最典型的应用是创建代理对象。

在这里插入图片描述

除此之外,在容器的创建过程中,还有两个与初始化相关的流程。一是确保所有单例都被实例化,这一点由 ConfigurableBeanFactory 接口的 preInstantiateSingletons 方法来实现。二是 SmartInitializingSingleton 接口,作用是在全部的单例创建完毕后执行某些操作,这一机制有效避免了提前实例化可能产生的副作用。

7. 项目信息

新增修改一览,新增(12),修改(4)。

beans
└─ src
   ├─ main
   │  └─ java
   │     └─ cn.stimd.spring.beans
   │        └─ factory
   │           ├─ annotation
   │           │  ├─ AutowiredAnnotationBeanPostProcessor.java (*)
   │           │  └─ InitDestroyAnnotationBeanPostProcessor.java (+)
   │           ├─ config
   │           │  ├─ ConfigurableBeanFactory.java (*)
   │           │  └─ DestructionAwareBeanPostProcessor.java (+)
   │           ├─ support
   │           │  ├─ AbstractAutowireCapableBeanFactory.java (*)
   │           │  └─ DefaultListableBeanFactory.java (*)
   │           ├─ Aware.java (+)
   │           ├─ BeanFactoryAware.java (+)
   │           ├─ InitializingBean.java (+)
   │           └─ SmartInitializingSingleton.java (+)
   └─ test
      └─ java
         └─ beans
            └─ lifecycle
               ├─ ITarget.java (+)
               ├─ LifecycleTest.java (+)
               ├─ MyInitBean.java (+)
               ├─ SimpleProxy.java (+)
               ├─ SimpleProxyPostProcessor.java (+)
               └─ TargetBean.java (+)

注:+号表示新增、*表示修改
  • 项目地址:https://gitee.com/stimd/spring-wheel

  • 本节分支:https://gitee.com/stimd/spring-wheel/tree/chapter1-8

注:项目的 master 分支会跟随教程的进度不断更新,如果想查看某一节的代码,请选择对应小节的分支代码。


关注公众号【Java编程探微】,加群一起讨论。
在这里插入图片描述

  • 12
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值