1. 概述
Spring框架中,一旦把一个Bean纳入Spring IOC容器之中,这个Bean的生命周期就会交由容器进行管理,所以准确的了解Spring Bean的生命周期是非常必要的。我们通常使用ApplicationContext作为Spring容器。这里,我们讲的也是 ApplicationContext中Bean的生命周期。
Spring是通过一系列的回调方法来完成 Bean 的生命周期管理的,开发者通过实现Spring的InitializeingBean
和DisposableBean
接口,就可以让容器来介入 Bean 生命周期的管理,主要是初始化阶段和销毁阶段的处理。除了初始化和销毁回调,Spring管理的对象也实现了Lifecycle
接口来让管理的对象在容器的生命周期内启动和关闭。
JSR-250的
@PostConstruct
和@PreDestroy
注解就是现代Spring应用生命周期回调的最佳实践。使用这些注解意味着Bean不在耦合在Spring特定的接口上。
如果开发者不想使用JSR-250的注解,仍然可以考虑使用init-method
和destroy-method
定义来解耦。
内部来说,Spring框架使用BeanPostProcessor
的实现来处理任何接口的回调,BeanPostProcessor
能够找到并调用合适的方法。如果开发者需要一些Spring并不直接提供的生命周期行为,开发者可以自行实现一个BeanPostProcessor
。
本文主要介绍我们常见的一些生命周期的管理方式。
2. 生命周期
Spring Bean的完整生命周期从创建Spring容器开始,直到最终Spring容器销毁Bean,这其中包含了一系列关键点,如下图所示:
可以看到,Bean 的整个生命周期相关的过程主要分为如下几种类型:
- Bean级生命周期接口:包括Bean本身调用的方法和通过配置文件中
<bean>
的 init-method 和 destroy-method指定的方法,InitializingBean
和DiposableBean
的接口实现; - 容器级生命周期接口:包括了
InstantiationAwareBeanPostProcessor
和BeanPostProcessor
以及LifecycleProcessor
这三个接口实现,一般称它们的实现类为“后置处理器”; - 工厂后置处理器接口:包括了
BeanFactoryPostProcessor
的各种实现,比如PropertyPlaceholderConfigurer
,CustomEditorConfigurer
,PropertyOverrideConfigurer
等等非常有用的工厂后置处理器接口的方法。工厂后处理器也是容器级的。在应用上下文装配配置文件之后立即调用。
接下来我们将分别介绍这几类生命周期的相关内容。
3. Bean 级生命周期
2.1 初始化回调
org.springframework.beans.factory.InitializingBean
接口允许Bean在所有的必要的依赖配置配置完成后来执行初始化Bean的操作。InitializingBean
接口中特指了一个方法:
1 | void afterPropertiesSet() throws Exception; |
Spring团队建议开发者不要使用InitializingBean
接口,因为这样会不必要的将代码耦合到Spring之上(其实问题也不大,现实场景中我们的应用一般也不会脱离 Spring 框架,这么说只是为了理想中的应用设计模式),而通过使用@PostConstruct
注解或者指定一个POJO的实现方法,比实现接口要更好。在基于XML的配置元数据上,开发者可以使用init-method
属性来指定一个没有参数的方法。使用Java配置的开发者可以使用@Bean
之中的initMethod
属性,比如如下:
1 | <bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/> |
1 | public class ExampleBean { |
与如下代码一样效果:
1 | <bean id="exampleInitBean" class="examples.AnotherExampleBean"/> |
1 | public class AnotherExampleBean implements InitializingBean { |
但是前一个版本的代码是没有耦合到Spring的。
2.2 销毁回调
实现了org.springframework.beans.factory.DisposableBean
接口的Bean就能通让容器通过回调来销毁Bean所用的资源。DisposableBean
接口包含了一个方法:
1 | void destroy() throws Exception; |
同 InitializingBean
一样,Spring团队仍然不建议开发者来使用DisposableBean
回调接口,因为这样会将开发者的代码耦合到Spring代码上。换种方式,比如使用@PreDestroy
注解或者指定一个Bean支持的配置方法,比如在基于XML的配置元数据中,开发者可以在Bean标签上指定destroy-method
属性。而在Java配置中,开发者可以配置@Bean
的destroyMethod
。
1 | <bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/> |
1 | public class ExampleBean { |
上面的代码配置和如下配置是等同的:
1 | <bean id="exampleInitBean" class="examples.AnotherExampleBean"/> |
1 | public class AnotherExampleBean implements DisposableBean { |
但是第一段代码是没有耦合到Spring的。
<bean/>
标签的destroy-method
可以被配置为特殊指定的值,来方便让Spring能够自动的检查到close
或者shutdown
方法(可以实现java.lang.AutoCloseable
或者java.io.Closeable
都会匹配。)这个特殊指定的值可以配置到<beans/>
的default-destroy-method
来让所有的Bean实现这个行为。
3.3 默认初始化和销毁方法
当开发者不使用Spring特有的InitializingBean
和DisposableBean
回调接口来实现初始化和销毁方法的时候,开发者通常定义的方法名字都是好似init()
,initialize()
或者是dispose()
等等。那么,想这类的方法就可以标准化,来让所有的开发者都使用一样的名字来确保一致性。
开发者可以配置Spring容器来针对每一个Bean都查找这种名字的初始化和销毁回调函数。也就是说,任何的一个应用开发者,都会在应用的类中使用一个叫init()
的初始化回调,而不需要在每个Bean中定义init-method="init"
这中属性。Spring IoC容器会在Bean创建的时候调用那个方法(就如前面描述的标准生命周期一样。)这个特性也强制开发者为其他的初始化以及销毁回调函数使用同样的名字。
假设开发者的初始化回调方法名字为init()
而销毁的回调方法为destroy()
。那么开发者的类就会好似如下的代码:
1 | public class DefaultBlogService implements BlogService { |
1 | <beans default-init-method="init"> |
<beans/>
标签上面的default-init-method
属性会让Spring IoC容器识别叫做init
的方法来作为Bean的初始化回调方法。当Bean创建和装载之后,如果Bean有这么一个方法的话,Spring容器就会在合适的时候调用。
类似的,开发者也可以配置默认销毁回调函数,基于XML的配置就在<beans/>
标签上面使用default-destroy-method
属性。
当存在一些Bean的类有了一些回调函数,而和配置的默认回调函数不同的时候,开发者可以通过特指的方式来覆盖掉默认的回调函数。以XML为例,就是通过使用<bean>
标签的init-method
和destroy-method
来覆盖掉<beans/>
中的配置。
Spring容器会做出如下保证,Bean会在装载了所有的依赖以后,立刻就开始执行初始化回调。这样的话,初始化回调只会在直接的Bean引用装载好后调用,而AOP拦截器还没有应用到Bean上。首先目标Bean会完全初始化好,然后,AOP代理以及其拦截链才能应用。如果目标Bean以及代理是分开定义的,那么开发者的代码甚至可以跳过AOP而直接和引用的Bean交互。因此,在初始化方法中应用拦截器会前后矛盾,因为这样做耦合了目标Bean的生命周期和代理/拦截器,还会因为同Bean直接交互而产生奇怪的现象。
3.4 联合生命周期机制
在Spring 2.5之后,开发者有三种选择来控制Bean的生命周期行为:
InitializingBean
和DisposableBean
回调接口- 自定义的
init()
以及destroy
方法 - 使用
@PostConstruct
以及@PreDestroy
注解
开发者也可以在Bean上联合这些机制一起使用
如果Bean配置了多个生命周期机制,而且每个机制配置了不同的方法名字,那么每个配置的方法会按照后面描述的顺序来执行。然而,如果配置了相同的名字,比如说初始化回调为
init()
,在不止一个生命周期机制配置为这个方法的情况下,这个方法只会执行一次。
如果一个Bean配置了多个生命周期机制,并且含有不同的方法名,执行的顺序如下:
- 包含
@PostConstruct
注解的方法 - 在
InitializingBean
接口中的afterPropertiesSet()
方法 - 自定义的
init()
方法
销毁方法的执行顺序和初始化的执行顺序相同:
- 包含
@PreDestroy
注解的方法 - 在
DisposableBean
接口中的destroy()
方法 - 自定义的
destroy()
方法
4. 容器级生命周期
4.1 Bean后置处理器BeanPostProcessor
这个接口主要用于在Spring容器中完成bean实例化、配置以及其他初始化方法前后要添加一些逻辑处理,它的接口定义如下所示:
1 | public interface BeanPostProcessor { |
BeanPostProcessor
的主要特征是它将逐个处理IoC容器中的所有bean实例,而不仅仅是单个bean实例,通常用于根据特定标准检查bean属性的有效性或改变bean属性。
BeanPostProcessor
在 Bean 生命周期中的位置如下:
- 通过构造函数或工厂方法创建Bean 实例
- 设置Bean 属性的值和bean引用
- 调用所有
Aware
接口中的Setter
方法 - 将Bean 实例传递给
postProcessBeforeInitialization()
每个bean后处理器的方法 - 调用初始化回调方法
- 将Bean 实例传递给
postProcessAfterInitialization()
每个bean后处理器的方法 - Bean 初始化完成
- 关闭容器时,调用销毁回调方法
4.2 实例后置处理器InstantiationAwareBeanPostProcessor
这个接口是 BeanPostProcessor
接口的一个子类,它多了三个方法,主要是添加了 Bean 实例化 的前后回调,注意,此时,所有的属性及依赖都还没设置。三个方法分别如下:
1 | // postProcessBeforeInstantiation方法的作用在目标对象被实例化之前调用的方法,可以返回目标实例的一个代理用来代替目标实例 |
postProcessBeforeInstantiation
:方法是最先执行的方法,它在目标对象实例化之前调用,该方法的返回值类型是Object,我们可以返回任何类型的值。由于这个时候目标对象还未实例化,所以这个返回值可以用来代替原本该生成的目标对象的实例(比如代理对象)。如果该方法的返回值代替原本该生成的目标对象,后续只有postProcessAfterInitialization方法会调用,其它方法不再调用;否则按照正常的流程走。postProcessAfterInstantiation
:方法在目标对象实例化之后调用,这个时候对象已经被实例化,但是该实例的属性还未被设置,都是null。如果该方法返回false,会忽略属性值的设置;如果返回true,会按照正常流程设置属性值。
5. 工厂后置处理器
5.1 接口定义
1 | public interface BeanFactoryPostProcessor { |
实现该接口,可以在Spring的Bean创建之前,修改Bean的定义属性。也就是说,Spring允许BeanFactoryPostProcessor
在容器实例化任何其它Bean之前读取配置元数据,并可以根据需要进行修改,例如可以把Bean的 scope 从 singleton 改为 prototype,也可以把 property 的值给修改掉。可以同时配置多个BeanFactoryPostProcessor
,并通过设置’order’属性来控制各个BeanFactoryPostProcessor的执行次序。
注意:
BeanFactoryPostProcessor
是在spring容器加载了bean的定义文件之后,在bean实例化之前执行的。接口方法的入参是ConfigurrableListableBeanFactory
,使用该参数,可以获取到相关bean的定义信息。
5.2 示例
如果我们有一个 Spring Bean 的定义如下:
1 |
|
我们编写一个自定义的 BeanFactoryPostProcessor
如下所示:
1 | public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { |
在这个 PostProcessor 中,我们将 foobar 这个 Bean 的属性值修改了,同时还修改了它的 scope 信息。
然后在我们的 XML 配置中把这个 Processor 注册进去即可:
1 |
|
我们常见的 BeanFactoryPostProcessor 的关系如下图所示:
6. Lifecycle 接口
严格来说,这部分内容并不属于 Spring Bean 的生命周期讨论的范围,这是一个更高阶更基础的接口,用来定义一些更抽象的生命周期及状态的。
Lifecycle
接口中为任何有自己生命周期需求的对象定义了基本的方法(比如启动和停止一些后台进程):
1 | public interface Lifecycle { |
任何Spring管理的对象都可实现上面的接口。那么当ApplicationContext
本身受到了启动或者停止的信号时,ApplicationContext
会通过委托LifecycleProcessor
来串联上下文中的Lifecycle
的实现。
1 | public interface LifecycleProcessor extends Lifecycle { |
从上面代码我们可以发现LifecycleProcessor
是Lifecycle
接口的扩展。LifecycleProcessor
增加了另外的两个方法来针对上下文的刷新和关闭做出反应。
常规的
org.springframework.context.Lifecycle
接口只是为明确的开始/停止通知提供一个契约,而并不表示在上下文刷新时自动开始。考虑实现org.springframework.context.SmartLifecycle
接口可以取代在某个Bean的自动启动过程(包括启动阶段)中的细粒度控制。同时,停止通知并不能保证在销毁之前出现:在正常的关闭情况下,所有的Lifecycle
Bean都会在销毁回调准备好之前收到停止停止,然而,在上下文存活期的热刷新或者停止刷新尝试的时候,只会调用销毁方法。
启动和关闭调用是很重要的。如果不同的Bean之间存在depends-on
的关系的话,被依赖的一方需要更早的启动,而且关闭的更早。然而,有的时候直接的依赖是未知的,而开发者仅仅知道哪一种类型需要更早进行初始化。在这种情况下,SmartLifecycle
接口定义了另一种选项,就是其父接口Phased
中的getPhase()
方法。
1 | public interface Phased { |
当启动时,拥有最低的phased
的对象优先启动,而当关闭时,是相反的顺序。因此,如果一个对象实现了SmartLifecycle
然后令其getPhase()
方法返回了Integer.MIN_VALUE
的话,就会让该对象最早启动,而最晚销毁。显然,如果getPhase()
方法返回了Integer.MAX_VALUE
就说明了该对象会最晚启动,而最早销毁。当考虑到使用phased
的值得时候,也同时需要了解正常没有实现SmartLifecycle
的Lifecycle
对象的默认值,这个值为0。因此,任何负值将标兵对象会在标准组件启动之前启动,在标准组件销毁以后再进行销毁。
SmartLifecycle
接口也定义了一个stop
的回调函数。任何实现了SmartLifecycle
接口的函数都必须在关闭流程完成之后调用回调中的run()
方法。这样做可以是能异步关闭。而LifecycleProcessor
的默认实现DefaultLifecycleProcessor
会等到配置的超时时间之后再调用回调。默认的每一阶段的超时时间为30秒。开发者可以通过定义一个叫做lifecycleProcessor
的Bean来覆盖默认的生命周期处理器。如果开发者需要配置超时时间,可以通过如下代码:
1 | <bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor"> |
和前文提到的,LifecycleProcessor
接口定义了回调方法来刷新和关闭上下文。关闭的话,如果stop()
方法已经明确调用了,那么就会驱动关闭的流程,但是如果是上下文关闭就不会发生这种情况。而刷新的回调会使能SmartLifecycle
的另一个特性。当上下文刷新完毕(所有的对象已经实例化并初始化),那么就会调用回调,默认的生命周期处理器会检查每一个SmartLifecycle
对象的isAutoStartup()
返回的Bool值。如果为真,对象将会自动启动而不是等待明确的上下文调用,或者调用自己的start()
方法(不同于上下文刷新,标准的上下文实现是不会自动启动的)。phased
的值以及depends-on
关系会决定对象启动和销毁的顺序。