分析Spring中Bean的生命周期

写在最前

从十一到现在两周,IOC部分源码读了一半多,是力气活,也是个精细活。全部写出来心有余力不足,所以找些重点写写。

简述 bean 实例化之前

Spring中不是启动容器时就开启bean的实例化进程,它首先会对资源进行读取并对bean进行初始化。

Spring将资源的定义和资源的加载区分开,Resource定义了统一的资源,默认实现是AbstractResource,资源的加载由ResourceLoader接口的不同实现类返回Resource。紧接着,解析Resource资源,将用户定义的Bean装载成BeanDefinition,每一个Bean对象都对应着一个BeanDefinitionBeanDefinition存在于IOC内部容器中的HashMap结构。再紧跟着,向IOC容器注册解析好的BeanDefinition,这个过程是通过BeanDefinitionRegistry接口来实现的。

初始化之后,会进行真正bean的加载,因为BeanDefinition不是想要的bean。初始化默认为懒加载,第一次调用getBean()时进行初始化。

首先得到可使用正确的beanName,这是涉及到别名或者factoryBean 。bean的作用域有singleton,prototype 和其他,Spring先尝试从缓存中加载单例bean,否则开始创建bean的实例。创建bean的实例可以理解为将BeanDefinition转换为BeanWrapper,BeanWarpper提供了get、set方法。之后还有一系列处理:MergedBeanDefinitionPostProcessor 属性合并,单例模式的循环依赖处理,属性填充,初始化bean。

在初始化bean这个过程中的代码,就包含了对XXXAware接口的处理,后置处理器,以及自定义的init-method方法。

Bean生命周期

回到最开始,看一下spring bean 的生命周期:

img
  1. 实例化Bean

    对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。

    对于ApplicationContext容器,当容器启动结束后,便实例化所有的bean。

    容器通过获取BeanDefination对象中的信息进行实例化。并且这一步仅仅是简单的实例化,并未进行依赖注入。

    实例化对象被包装在BeanWrapper对象中,BeanWrapper提供了设置对象属性的接口,从而避免了使用反射机制设置属性。

    在实例化bean的过程中,Spring采用策略模式决定采用反射还是 CGLIB 动态字节码。Spring 默认采用 CglibSubclassingInstantiationStrategy实例化bean,他既可以以反射实例化对象,还可以通过 CGLIB 的动态字节码的方式,以方法(setXxx)的方式注入对象实例化。

  2. 设置对象属性(依赖注入)

    实例化后的对象被封装在BeanWrapper对象中,并且此时对象仍然是一个原生的状态,并没有进行依赖注入。

    紧接着,Spring根据BeanDefinition中的信息进行依赖注入。

    并且通过BeanWrapper提供的设置属性的接口完成依赖注入。

  3. 注入Aware接口

    紧接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给bean。

    private void invokeAwareMethods(final String beanName, final Object bean) {
        if (bean instanceof Aware) {
            if (bean instanceof BeanNameAware) {
                ((BeanNameAware) bean).setBeanName(beanName);
            }
            if (bean instanceof BeanClassLoaderAware) {
                ClassLoader bcl = getBeanClassLoader();
                if (bcl != null) {
                    ((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);
                }
            }
            if (bean instanceof BeanFactoryAware) {
                ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
            }
        }
    }
    
  4. BeanPostProcessor

    当经过上述几个步骤后,bean对象已经被正确构造,但如果你想要对象被使用前再进行一次自定义的处理,就可以通过BeanPostProcessor接口实现。

    该接口提供了两个函数:

    • postProcessBeforInitialzation(Object bean, String beanName)

      当前正在初始化的bean对象会被传递进来,我们就可以对这个bean做任何处理。

      这个函数会先于InitialzationBean执行,因此称为前置处理。

      所有Aware接口的注入就是在这一步完成的。

    • postProcessAfterInitialzation(Object bean, String beanName)

      当前正在初始化的bean对象会被传递进来,我们就可以对这个bean做任何处理。

      这个函数会在initialzationBean完成后执行,因此成为后置处理

      public interface BeanPostProcessor {
          @Nullable
          default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
              return bean;
          }
      
          @Nullable
          default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
              return bean;
          }
      }
      
  1. InitializingBean 与 init-method

    当BeanPostProcessor的前置处理完成后就会进入本阶段。

    InitialzingBean接口只有一个函数:

    • afterPropertiesSet()

    这一阶段也可以在bean正式构造完成前增加我们自定义的逻辑,但它与前置处理不同,由于该函数并不会把当前bean传递进来,因此这一步没办法处理对象本身,只能增加一些额外的逻辑。若要使用它,我们需要让bean实现该接口,并把要增加的逻辑写在该函数中。然后Spring会在前置处理完成后检测当前bean是否实现了该接口,并执行afterPropertiesSet函数。

    当然,Spring为了降低对客户代码的侵入性,给bean的配置提供了init-method属性,该属性指定了在这一阶段需要执行的函数名。Spring便会在初始化阶段执行我们设置的函数。init-method本质上仍然使用了InitializingBean的接口。

    protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
        throws Throwable {
    
        boolean isInitializingBean = (bean instanceof InitializingBean);
        if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
            if (logger.isDebugEnabled()) {
                logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
            }
            if (System.getSecurityManager() != null) {
                try {
                    AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
                        ((InitializingBean) bean).afterPropertiesSet();
                        return null;
                    }, getAccessControlContext());
                }
                catch (PrivilegedActionException pae) {
                    throw pae.getException();
                }
            }
            else {
                ((InitializingBean) bean).afterPropertiesSet();
            }
        }
    
        if (mbd != null && bean.getClass() != NullBean.class) {
            String initMethodName = mbd.getInitMethodName();
            if (StringUtils.hasLength(initMethodName) &&
                !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
                !mbd.isExternallyManagedInitMethod(initMethodName)) {
                invokeCustomInitMethod(beanName, bean, mbd);
            }
        }
    }
    
  1. DisposableBean 和 destroy-method

    和init-method一样,通过给destroy-method指定函数,就可以在bean销毁前执行指定的逻辑。

实例代码

先来一段代码,看看Spring中Bean的生命周期

public class LifeCycleBean implements BeanNameAware, BeanFactoryAware, BeanClassLoaderAware, InitializingBean,DisposableBean  {
    private String test;

    public LifeCycleBean() {
        System.out.println("构造函数调用...");
    }

    public String getTest() {
        return test;
    }

    public void display(){
        System.out.println("方法调用...");
    }

    public void setTest(String test) {
        System.out.println("属性注入....");
        this.test = test;
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("BeanNameAware 被调用...");
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        System.out.println("BeanClassLoaderAware 被调用...");
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("BeanFactoryAware 被调用...");
    }

    public void initMethod(){
        System.out.println("init-method 被调用...");
    }

    public void destroyMethod(){
        System.out.println("destroy-method 被调用...");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean afterPropertiesSet 被调动...");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("DisposableBean destroy 被调动...");
    }
}
<bean id="lifeCycleBean" class="com.example.bean.LifeCycleBean"
      init-method="initMethod" destroy-method="destroyMethod">
    <property name="test" value="test"/>
</bean>
public static void main(String[] args) {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/applicationContext.xml");

    LifeCycleBean lifeCycleBean = (LifeCycleBean) context.getBean("lifeCycleBean");
    lifeCycleBean.display();

    context.destroy();
}

运行结果如下:

构造函数调用...
属性注入....
BeanNameAware 被调用...
BeanClassLoaderAware 被调用...
BeanFactoryAware 被调用...
InitializingBean afterPropertiesSet 被调动...
init-method 被调用...
方法调用...
DisposableBean destroy 被调动...
destroy-method 被调用...
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容