Spring 中 BeanFactory 的生命周期与源码解析(附例程)

参考地址:

《Spring核心之BeanFactory 一张图看懂getBean全过程》
《Spring bean的生命周期,对比源码详解》
《Spring源码学习--Bean的生命周期》
《AbstractApplicationContext.refresh()》
《容器的功能扩展(三)finishRefresh》
《spring中InitializingBean接口使用理解》

参考书籍:

《Spring+3.x企业应用开发实战》


前段时间项目中用 EhCache 缓存,其中为某些 Bean 添加缓存的工作放到了 InitializingBean 接口的 afterPropertiesSet() 方法中。对该方法一直都表示很奇怪,BeanFactory 的生命周期部分内容笔者也觉得应该重点学习一下,所以有时间就总结学习一下。
本文将从一个例程说明 BeanFactory 的生命周期的起始到终结。

一. BeanFactory 例程

笔者以 《Spring源码学习--Bean的生命周期》 中的例程为例。笔者也将其上传到笔者的 github 账号上,可以从上面下载并使用 idea 进行实验。
笔者建议,在 IDE 中建立链接中工程完毕后,一定要在每一个方法里面打上断点,从 main 方法开始运行并观察程序运行的流程。

链接中的源码不再赘述。源码运行结果为:

现在开始初始化容器
八月 15, 2018 10:27:00 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7f560810: startup date [Wed Aug 15 22:27:00 CST 2018]; root of context hierarchy
八月 15, 2018 10:27:00 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [beanLife.xml]

这是BeanFactoryPostProcessor实现类构造器!!
BeanFactoryPostProcessor调用postProcessBeanFactory方法
这是BeanPostProcessor实现类构造器!!
这是InstantiationAwareBeanPostProcessorAdapter实现类构造器!!
InstantiationAwareBeanPostProcessor调用postProcessBeforeInstantiation方法
【构造器】调用Person的构造器实例化
InstantiationAwareBeanPostProcessor调用postProcessPropertyValues方法
【注入属性】注入属性address
【注入属性】注入属性name
【注入属性】注入属性phone
【BeanNameAware接口】调用BeanNameAware.setBeanName()
【BeanFactoryAware接口】调用BeanFactoryAware.setBeanFactory()
BeanPostProcessor接口方法postProcessBeforeInitialization对属性进行更改!
【InitializingBean接口】调用InitializingBean.afterPropertiesSet()
【init-method】调用<bean>的init-method属性指定的初始化方法
BeanPostProcessor接口方法postProcessAfterInitialization对属性进行更改!
InstantiationAwareBeanPostProcessor调用postProcessAfterInitialization方法
容器初始化成功
Person [address=广州, name=张三, phone=110]
现在开始关闭容器!
【DiposibleBean接口】调用DiposibleBean.destory()
【destroy-method】调用<bean>的destroy-method属性指定的初始化方法

注:
准确的说,该例程的应用上下文环境是 ApplicationContext,所以讲的不完全是 BeanFactory 的生命周期。但 ApplicationContext 是 BeanFactory 的派生实现类,且 ApplicationContext 的生命周期与 BeanFactory 生命周期十分相近,所以还是用该例程进行说明即可。

二. 从例程看 BeanFactory 中 Bean 的生命周期

在《Spring+3.x企业应用开发实战》一书中,用一幅图描述了 BeanFactory 中 Bean 生命周期的完整过程:

图 1.1 BeanFactory 中 Bean 的生命周期

笔者用上面的例程调试了一下,发现输出结果是与上图中的流程不相称的。所以笔者总结了一下从 main 方法开始运行到 bean 的销毁整个流程如下:

2.1 ApplicationContext 的初始化

  1. main 函数:
    • ApplicationContext factory = new ClassPathXmlApplicationContext("beanLife.xml");

从 main 函数开始,第一句代码定义了一个 classpath 路径为基准的应用上下文。也就是说,定义了 bean 文件的 xml 文件只有放在 web-info/classes 目录下的配置文件才有效果

2.2 BeanFactoryPostProcessor

然后代码进入了 AbstractApplicationContext 中。在 ClassPathXmlApplicationContext 中,初始化的核心代码在 AbstractApplicationContext 的 refresh() 方法中:


...
try {
    this.postProcessBeanFactory(beanFactory);
    this.invokeBeanFactoryPostProcessors(beanFactory);
    this.registerBeanPostProcessors(beanFactory);
    this.initMessageSource();
    this.initApplicationEventMulticaster();
    this.onRefresh();
    this.registerListeners();
    this.finishBeanFactoryInitialization(beanFactory);
    this.finishRefresh();
}
...

  1. AbstractApplicationContext:
    • invokeBeanFactoryPostProcessors(beanFactory): 调用 Bean 工厂后处理器;
  2. AbstractApplicationContext # doGetBean:
    • beanName = "beanFactoryPostProcessor";
    • 在 invokeBeanFactoryPostProcessors() 方法中载入 id 为 beanFactoryPostProcessor 的 bean,在 beanLife.xml 中,该 bean 对应的类为我们自定义的 MyBeanFactoryPostProcessor;
  3. 构造函数:MyBeanFactoryPostProcessor;
  4. MyBeanFactoryPostProcessor # postProcessBeanFactory:
    • MyBeanFactoryPostProcessor 实现 BeanFactoryPostProcessor 接口;
    • BeanDefinition bd = arg0.getBeanDefinition("person"):
      • (从 beanLife.xml 中)获取名为 person 的 bean 定义;同理如果参数为 "computer",也可以获取 computer 的 bean 定义;
    • bd.getPropertyValues().addPropertyValue("phone", "110");
      • 获得了 bean 定义后,可以设置属性值;

2.3 BeanPostProcessor 与 InstantiationAwareBeanPostProcessor

BeanFactoryPostProcessor 完成后,应用上下文的 refresh() 方法运行至 registerBeanPostProcessors,进行 BeanPostProcessor 的初始化:

  1. AbstractApplicationContext # registerBeanPostProcessors;
    • 注册 Bean 后处理器;
  2. AbstractApplicationContext # doGetBean:
    • beanName = "beanPostProcessor";
    • registerBeanPostProcessors() 方法中载入 id 为 beanPostProcessor 的 bean,在 beanLife.xml 中,该 bean 对应的类为我们自定义的 MyBeanPostProcessor;
  3. 构造函数:MyBeanPostProcessor;
  4. AbstractApplicationContext # doGetBean:
    • beanName = "inistantiationAwareBeanPostProcessor";
    • registerBeanPostProcessors() 中,载入 id 为 inistantiationAwareBeanPostProcessor 的 bean,在 beanLife.xml 中,该 bean 对应的类为我们自定义的 MyInstantiationAwareBeanPostProcessor;
  5. 构造函数:MyInstantiationAwareBeanPostProcessor;
  6. AbstractApplicationContext # finishBeanFactoryInitialization();
    • 完成 BeanFactory 的初始化工作;
    • 此后,BeanFactory 冻结所有的 Bean 定义,不再可以修改或者做 post process 操作;

注:关于 AbstractApplicationContext # refresh() 方法详细流程见:《AbstractApplicationContext.refresh()》

2.4 xml 文件 bean 的实例化与初始化

截止到上面,应用上下文已经装载完毕,上下文将对 xml 文件中的 bean 中进行实例化。beanLife.xml 配置文件中有 Person 和 Computer 两个 bean,这里以 Person 为例进行说明。

首先是实例化,即 Person 构造函数的调用。

  1. MyInstantiationAwareBeanPostProcessor # postProcessBeforeInstantiation:
    • 实例化之前的处理;
  2. 构造函数:Person;
    • 注意到,即使此时已经调用了 Person 的构造函数,但实际上 main 函数并没有运行到 Person 的 getBean 阶段。说明 main 函数获取 bean 之前,bean 已经在应用上下文中装载完毕

然后是初始化,即 Person 属性注入的过程。

  1. MyInstantiationAwareBeanPostProcessor # postProcessPropertyValues:
    • 为初始化的对象注入属性;
  2. Person 注入属性;
    • 分别调用 Person 属性值 name, address, phone 的 set 方法;
  3. BeanNameAware # setBeanName
    • Person 实现了 BeanNameAware 接口,则调用该接口方法 setBeanName;
  4. BeanFactoryAware # setBeanFactory
    • Person 实现了 BeanFactoryAware 接口,则调用该接口方法 setBeanFactory;
  5. MyBeanPostProcessor # postProcessBeforeInitialization:
    • 根据《Spring+3.x 企业应用开发实战》一书中所述:BeanPostProcessor 在 Spring 框架中占有重要的地位,它为容器提供对 Bean 进行后续加工处理的切入点,Spring 容器所提供的各种功能(如 AOP,动态代理等),都通过 BeanPostProcessor 实施;
  6. InitializingBean # afterPropertiesSet():
    • 初始化方法一;
  7. beanLife.xml 的 Person 的 init-method 方法:
    • 初始化方法二;
  8. MyBeanPostProcessor # postProcessAfterInitialization:
  9. MyInstantiationAwareBeanPostProcessor # postProcessAfterInitialization

经历了上面的实例化 (Instantiation)初始化 (Initialization) 之后,一个 bean 就创建完成了。
与 person 的 bean 实例化过程相同,剩下一个 id 为 computer 的 bean 实例化与初始化的过程如下:

  1. MyInstantiationAwareBeanPostProcessor # postProcessBeforeInstantiation
  2. 构造函数:Computer
  3. MyInstantiationAwareBeanPostProcessor # postProcessPropertyValues
  4. Computer 注入属性;
  5. BeanNameAware # setBeanName
  6. BeanFactoryAware # setBeanFactory
  7. MyBeanPostProcessor # postProcessBeforeInitialization
  8. InitializingBean # afterPropertiesSet()
  9. beanLife.xml 的 Computer 的 init-method 方法
  10. MyBeanPostProcessor # postProcessAfterInitialization
  11. MyInstantiationAwareBeanPostProcessor # postProcessAfterInitialization

注:关于两种初始化方法 (afterPropertiesSet 与 init-method 指定方法) 的区别:

  1. spring为bean提供了两种初始化bean的方式,实现InitializingBean接口,实现afterPropertiesSet方法,或者在配置文件中同过init-method指定,两种方式可以同时使用;
  2. 实现InitializingBean接口是直接调用afterPropertiesSet方法,比通过反射调用init-method指定的方法效率相对来说要高点。但是init-method方式消除了对spring的依赖;
  3. 如果调用afterPropertiesSet方法时出错,则不调用init-method指定的方法。

2.5 finishRefresh

  1. AbstractApplicationContext # finishRefresh
  2. 第四次 doGetBean, beanName = "lifecycleProcessor";
  3. main # Person person = factory.getBean("person",Person.class);
  4. 第五次 doGetBean, beanName = "person";
  5. 第六次 doGetBean, beanName = "computer";
  6. main # ((ClassPathXmlApplicationContext)factory).registerShutdownHook();
  7. 第七次 doGetBean, beanName = "lifecycleProcessor"

2.6 main 调用 beans

此后进入 main 函数的 getBean 部分:

public static void main(String[] args) {
    ...
    
    //得到Preson,并使用
    Person person = context.getBean("person",Person.class);
    System.out.println(person);
    // 得到 Computer,并使用
    Computer computer = context.getBean("computer", Computer.class);
    System.out.println(computer);
    
    ...
}

从前面在 BeanPostProcessor 中已经将所有 bean 在应用上下文中实例化完成,在 main 函数这里只是从应用上下文中,通过应用上下文的 getBean 方法取出即可。

2.7 bean 的销毁

在基于 web 的 ApplicationContext 实现中,已有相应的实现来处理关闭 web 应用时恰当地关闭 Spring IoC 容器。但对于该例中的一个非 web 应用的环境下使用 Spring 的 IoC 容器,如果想让容器优雅的关闭,并调用 singleton 的 bean 相应 destory 回调方法,则需要在 JVM 里注册一个“关闭钩子” (shutdown hook)。这一点非常容易做到,并且将会确保你的 Spring IoC 容器被恰当关闭,以及所有由单例持有的资源都会被释放

为了注册“关闭钩子”,你只需要简单地调用在 AbstractApplicationContext 实现中的registerShutdownHook() 方法即可。

  1. main # registerShutdownHook();
    • main 函数中,AbstractApplicationContext 调用 registerShutdownHook() 方法,注册容器的关闭;
  2. Computer 的销毁
    • (1) DisposableBean(Computer) # destory
    • (2) beanLife.xml 的 Computer 的 destroy-method 方法
  3. Person 的销毁
    • (1) DisposableBean(Person) # destory
    • (2) beanLife.xml 的 Person 的 destroy-method 方法

至此,该程序结束。

三. 后记

Spring 生命周期的理解,对后面的事务处理、AOP 等重要特性有很大的帮助。但如果要理解生命周期,单看书是很难理解的,尤其是对着那些又长又多的类名,和它们那些又长又多又像的方法。所以笔者建议如果想要理解声明周期:

调试!!!

调试!!!

调试!!!

只有对着教程,运行代码的一步步调试,才能加深自己的印象。

笔者的工程,已经上传到了笔者的 github 账号上。
路径如下:https://github.com/upcAutoLang/SpringBeanLIfeCycleDemo

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

推荐阅读更多精彩内容

  • 1.1 Spring IoC容器和bean简介 本章介绍了Spring Framework实现的控制反转(IoC)...
    起名真是难阅读 2,535评论 0 8
  • 1.1 spring IoC容器和beans的简介 Spring 框架的最核心基础的功能是IoC(控制反转)容器,...
    simoscode阅读 6,633评论 2 22
  • 创建 bean 在经历过 AbstractAutowireCapableBeanFactory#createBea...
    仗剑诗篇阅读 2,140评论 0 1
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 盛夏傍晚,来到小河边纳凉。远离了汽车的轰鸣,空调的烦躁,坐在混着青草香味的空气里,顿时神清气爽。索性躺在河堤上,管...
    香凝绿荫阅读 554评论 1 6