Spring中Bean的生命周期

1. 概述

Spring框架中,一旦把一个Bean纳入Spring IOC容器之中,这个Bean的生命周期就会交由容器进行管理,所以准确的了解Spring Bean的生命周期是非常必要的。我们通常使用ApplicationContext作为Spring容器。这里,我们讲的也是 ApplicationContext中Bean的生命周期。

Spring是通过一系列的回调方法来完成 Bean 的生命周期管理的,开发者通过实现Spring的InitializeingBeanDisposableBean接口,就可以让容器来介入 Bean 生命周期的管理,主要是初始化阶段和销毁阶段的处理。除了初始化和销毁回调,Spring管理的对象也实现了Lifecycle接口来让管理的对象在容器的生命周期内启动和关闭。

JSR-250的@PostConstruct@PreDestroy注解就是现代Spring应用生命周期回调的最佳实践。使用这些注解意味着Bean不在耦合在Spring特定的接口上。
如果开发者不想使用JSR-250的注解,仍然可以考虑使用init-methoddestroy-method定义来解耦。

内部来说,Spring框架使用BeanPostProcessor的实现来处理任何接口的回调,BeanPostProcessor能够找到并调用合适的方法。如果开发者需要一些Spring并不直接提供的生命周期行为,开发者可以自行实现一个BeanPostProcessor

本文主要介绍我们常见的一些生命周期的管理方式。

2. 生命周期

Spring Bean的完整生命周期从创建Spring容器开始,直到最终Spring容器销毁Bean,这其中包含了一系列关键点,如下图所示:

可以看到,Bean 的整个生命周期相关的过程主要分为如下几种类型:

  • Bean级生命周期接口:包括Bean本身调用的方法和通过配置文件中<bean>init-methoddestroy-method指定的方法,InitializingBeanDiposableBean 的接口实现;
  • 容器级生命周期接口:包括了 InstantiationAwareBeanPostProcessorBeanPostProcessor 以及 LifecycleProcessor 这三个接口实现,一般称它们的实现类为“后置处理器”;
  • 工厂后置处理器接口:包括了BeanFactoryPostProcessor的各种实现,比如PropertyPlaceholderConfigurerCustomEditorConfigurerPropertyOverrideConfigurer 等等非常有用的工厂后置处理器接口的方法。工厂后处理器也是容器级的。在应用上下文装配配置文件之后立即调用。

接下来我们将分别介绍这几类生命周期的相关内容。

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
2
3
4
5
6
7
public class ExampleBean {

public void init() {
// do some initialization work
}

}

与如下代码一样效果:

1
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
1
2
3
4
5
6
7
public class AnotherExampleBean implements InitializingBean {

public void afterPropertiesSet() {
// do some initialization work
}

}

但是前一个版本的代码是没有耦合到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配置中,开发者可以配置@BeandestroyMethod

1
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
1
2
3
4
5
6
7
public class ExampleBean {

public void cleanup() {
// do some destruction work (like releasing pooled connections)
}

}

上面的代码配置和如下配置是等同的:

1
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
1
2
3
4
5
6
7
public class AnotherExampleBean implements DisposableBean {

public void destroy() {
// do some destruction work (like releasing pooled connections)
}

}

但是第一段代码是没有耦合到Spring的。

<bean/>标签的destroy-method可以被配置为特殊指定的值,来方便让Spring能够自动的检查到close或者shutdown方法(可以实现java.lang.AutoCloseable或者java.io.Closeable都会匹配。)这个特殊指定的值可以配置到<beans/>default-destroy-method来让所有的Bean实现这个行为。

3.3 默认初始化和销毁方法

当开发者不使用Spring特有的InitializingBeanDisposableBean回调接口来实现初始化和销毁方法的时候,开发者通常定义的方法名字都是好似init()initialize()或者是dispose()等等。那么,想这类的方法就可以标准化,来让所有的开发者都使用一样的名字来确保一致性。

开发者可以配置Spring容器来针对每一个Bean都查找这种名字的初始化和销毁回调函数。也就是说,任何的一个应用开发者,都会在应用的类中使用一个叫init()的初始化回调,而不需要在每个Bean中定义init-method="init"这中属性。Spring IoC容器会在Bean创建的时候调用那个方法(就如前面描述的标准生命周期一样。)这个特性也强制开发者为其他的初始化以及销毁回调函数使用同样的名字。

假设开发者的初始化回调方法名字为init()而销毁的回调方法为destroy()。那么开发者的类就会好似如下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DefaultBlogService implements BlogService {

private BlogDao blogDao;

public void setBlogDao(BlogDao blogDao) {
this.blogDao = blogDao;
}

// this is (unsurprisingly) the initialization callback method
public void init() {
if (this.blogDao == null) {
throw new IllegalStateException("The [blogDao] property must be set.");
}
}

}
1
2
3
4
5
6
7
<beans default-init-method="init">

<bean id="blogService" class="com.foo.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>

</beans>

<beans/>标签上面的default-init-method属性会让Spring IoC容器识别叫做init的方法来作为Bean的初始化回调方法。当Bean创建和装载之后,如果Bean有这么一个方法的话,Spring容器就会在合适的时候调用。

类似的,开发者也可以配置默认销毁回调函数,基于XML的配置就在<beans/>标签上面使用default-destroy-method属性。

当存在一些Bean的类有了一些回调函数,而和配置的默认回调函数不同的时候,开发者可以通过特指的方式来覆盖掉默认的回调函数。以XML为例,就是通过使用<bean>标签的init-methoddestroy-method来覆盖掉<beans/>中的配置。

Spring容器会做出如下保证,Bean会在装载了所有的依赖以后,立刻就开始执行初始化回调。这样的话,初始化回调只会在直接的Bean引用装载好后调用,而AOP拦截器还没有应用到Bean上。首先目标Bean会完全初始化好,然后,AOP代理以及其拦截链才能应用。如果目标Bean以及代理是分开定义的,那么开发者的代码甚至可以跳过AOP而直接和引用的Bean交互。因此,在初始化方法中应用拦截器会前后矛盾,因为这样做耦合了目标Bean的生命周期和代理/拦截器,还会因为同Bean直接交互而产生奇怪的现象。

3.4 联合生命周期机制

在Spring 2.5之后,开发者有三种选择来控制Bean的生命周期行为:

  • InitializingBeanDisposableBean回调接口
  • 自定义的init()以及destroy方法
  • 使用@PostConstruct以及@PreDestroy注解

开发者也可以在Bean上联合这些机制一起使用

如果Bean配置了多个生命周期机制,而且每个机制配置了不同的方法名字,那么每个配置的方法会按照后面描述的顺序来执行。然而,如果配置了相同的名字,比如说初始化回调为init(),在不止一个生命周期机制配置为这个方法的情况下,这个方法只会执行一次。

如果一个Bean配置了多个生命周期机制,并且含有不同的方法名,执行的顺序如下:

  • 包含@PostConstruct注解的方法
  • InitializingBean接口中的afterPropertiesSet()方法
  • 自定义的init()方法

销毁方法的执行顺序和初始化的执行顺序相同:

  • 包含@PreDestroy注解的方法
  • DisposableBean接口中的destroy()方法
  • 自定义的destroy()方法

4. 容器级生命周期

4.1 Bean后置处理器BeanPostProcessor

这个接口主要用于在Spring容器中完成bean实例化、配置以及其他初始化方法前后要添加一些逻辑处理,它的接口定义如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public interface BeanPostProcessor {

/**
* Apply this BeanPostProcessor to the given new bean instance <i>before</i> any bean
* initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
* or a custom init-method). The bean will already be populated with property values.
* The returned bean instance may be a wrapper around the original.
* @param bean the new bean instance
* @param beanName the name of the bean
* @return the bean instance to use, either the original or a wrapped one;
* if {@code null}, no subsequent BeanPostProcessors will be invoked
* @throws org.springframework.beans.BeansException in case of errors
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
*/
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;

/**
* Apply this BeanPostProcessor to the given new bean instance <i>after</i> any bean
* initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
* or a custom init-method). The bean will already be populated with property values.
* The returned bean instance may be a wrapper around the original.
* <p>In case of a FactoryBean, this callback will be invoked for both the FactoryBean
* instance and the objects created by the FactoryBean (as of Spring 2.0). The
* post-processor can decide whether to apply to either the FactoryBean or created
* objects or both through corresponding {@code bean instanceof FactoryBean} checks.
* <p>This callback will also be invoked after a short-circuiting triggered by a
* {@link InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation} method,
* in contrast to all other BeanPostProcessor callbacks.
* @param bean the new bean instance
* @param beanName the name of the bean
* @return the bean instance to use, either the original or a wrapped one;
* if {@code null}, no subsequent BeanPostProcessors will be invoked
* @throws org.springframework.beans.BeansException in case of errors
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
* @see org.springframework.beans.factory.FactoryBean
*/
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;

}

BeanPostProcessor的主要特征是它将逐个处理IoC容器中的所有bean实例,而不仅仅是单个bean实例,通常用于根据特定标准检查bean属性的有效性或改变bean属性

BeanPostProcessor在 Bean 生命周期中的位置如下:

  1. 通过构造函数或工厂方法创建Bean 实例
  2. 设置Bean 属性的值和bean引用
  3. 调用所有 Aware 接口中的 Setter 方法
  4. 将Bean 实例传递给postProcessBeforeInitialization()每个bean后处理器的方法
  5. 调用初始化回调方法
  6. 将Bean 实例传递给 postProcessAfterInitialization() 每个bean后处理器的方法
  7. Bean 初始化完成
  8. 关闭容器时,调用销毁回调方法

4.2 实例后置处理器InstantiationAwareBeanPostProcessor

这个接口是 BeanPostProcessor 接口的一个子类,它多了三个方法,主要是添加了 Bean 实例化 的前后回调,注意,此时,所有的属性及依赖都还没设置。三个方法分别如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// postProcessBeforeInstantiation方法的作用在目标对象被实例化之前调用的方法,可以返回目标实例的一个代理用来代替目标实例
// beanClass参数表示目标对象的类型,beanName是目标实例在Spring容器中的name
// 返回值类型是Object,如果返回的是非null对象,接下来除了postProcessAfterInitialization方法会被执行以外,其它bean构造的那些方法都不再执行。否则那些过程以及postProcessAfterInitialization方法都会执行
Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException;

// postProcessAfterInstantiation方法的作用在目标对象被实例化之后并且在属性值被populate之前调用
// bean参数是目标实例(这个时候目标对象已经被实例化但是该实例的属性还没有被设置),beanName是目标实例在Spring容器中的name
// 返回值是boolean类型,如果返回true,目标实例内部的返回值会被populate,否则populate这个过程会被忽视
boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException;

// postProcessPropertyValues方法的作用在属性中被设置到目标实例之前调用,可以修改属性的设置
// pvs参数表示参数属性值(从BeanDefinition中获取),pds代表参数的描述信息(比如参数名,类型等描述信息),bean参数是目标实例,beanName是目标实例在Spring容器中的name
// 返回值是PropertyValues,可以使用一个全新的PropertyValues代替原先的PropertyValues用来覆盖属性设置或者直接在参数pvs上修改。如果返回值是null,那么会忽略属性设置这个过程(所有属性不论使用什么注解,最后都是null)
PropertyValues postProcessPropertyValues(
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName)
throws BeansException;
  1. postProcessBeforeInstantiation:方法是最先执行的方法,它在目标对象实例化之前调用,该方法的返回值类型是Object,我们可以返回任何类型的值。由于这个时候目标对象还未实例化,所以这个返回值可以用来代替原本该生成的目标对象的实例(比如代理对象)。如果该方法的返回值代替原本该生成的目标对象,后续只有postProcessAfterInitialization方法会调用,其它方法不再调用;否则按照正常的流程走。
  2. postProcessAfterInstantiation:方法在目标对象实例化之后调用,这个时候对象已经被实例化,但是该实例的属性还未被设置,都是null。如果该方法返回false,会忽略属性值的设置;如果返回true,会按照正常流程设置属性值。

5. 工厂后置处理器

5.1 接口定义

1
2
3
4
5
6
7
8
9
10
11
public interface BeanFactoryPostProcessor {

/**
* Modify the application context's internal bean factory after its standard
* initialization. All bean definitions will have been loaded, but no beans
* will have been instantiated yet. This allows for overriding or adding
* properties even to eager-initializing beans.
* @param beanFactory the bean factory used by the application context
* @throws org.springframework.beans.BeansException in case of errors
*/
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

实现该接口,可以在Spring的Bean创建之前,修改Bean的定义属性。也就是说,Spring允许BeanFactoryPostProcessor在容器实例化任何其它Bean之前读取配置元数据,并可以根据需要进行修改,例如可以把Bean的 scopesingleton 改为 prototype,也可以把 property 的值给修改掉。可以同时配置多个BeanFactoryPostProcessor,并通过设置’order’属性来控制各个BeanFactoryPostProcessor的执行次序。

注意BeanFactoryPostProcessor是在spring容器加载了bean的定义文件之后,在bean实例化之前执行的。接口方法的入参是ConfigurrableListableBeanFactory,使用该参数,可以获取到相关bean的定义信息。

5.2 示例

如果我们有一个 Spring Bean 的定义如下:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8" ?>
<beans>

<bean id="foobar" class="com.doleje.demo.spring.model.FooBar">
<property name="desc" value="测试一下啦" />
<property name="remark" value="这是备注信息啦啦啦" />
</bean>

</beans>

我们编写一个自定义的 BeanFactoryPostProcessor如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        log.info("调用MyBeanFactoryPostProcessor的postProcessBeanFactory");
        BeanDefinition bd = beanFactory.getBeanDefinition("foobar");
        log.info("Properties: {}", bd.getPropertyValues().toString());
        MutablePropertyValues pv =  bd.getPropertyValues();  
        if (pv.contains("remark")) {  
            pv.addPropertyValue("remark", "把备注信息修改一下");  
        }
// 修改 scope
        bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
    }

}

在这个 PostProcessor 中,我们将 foobar 这个 Bean 的属性值修改了,同时还修改了它的 scope 信息。

然后在我们的 XML 配置中把这个 Processor 注册进去即可:

1
2
3
4
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<bean id="myBeanFactoryPostProcessor" class="comcom.doleje.demo.spring.processor.MyBeanFactoryPostProcessor" />
</beans>

我们常见的 BeanFactoryPostProcessor 的关系如下图所示:

img

6. Lifecycle 接口

严格来说,这部分内容并不属于 Spring Bean 的生命周期讨论的范围,这是一个更高阶更基础的接口,用来定义一些更抽象的生命周期及状态的。

Lifecycle接口中为任何有自己生命周期需求的对象定义了基本的方法(比如启动和停止一些后台进程):

1
2
3
4
5
6
7
8
9
public interface Lifecycle {

void start();

void stop();

boolean isRunning();

}

任何Spring管理的对象都可实现上面的接口。那么当ApplicationContext本身受到了启动或者停止的信号时,ApplicationContext会通过委托LifecycleProcessor来串联上下文中的Lifecycle的实现。

1
2
3
4
5
6
7
public interface LifecycleProcessor extends Lifecycle {

void onRefresh();

void onClose();

}

从上面代码我们可以发现LifecycleProcessorLifecycle接口的扩展。LifecycleProcessor增加了另外的两个方法来针对上下文的刷新和关闭做出反应。

常规的org.springframework.context.Lifecycle接口只是为明确的开始/停止通知提供一个契约,而并不表示在上下文刷新时自动开始。考虑实现org.springframework.context.SmartLifecycle接口可以取代在某个Bean的自动启动过程(包括启动阶段)中的细粒度控制。同时,停止通知并不能保证在销毁之前出现:在正常的关闭情况下,所有的LifecycleBean都会在销毁回调准备好之前收到停止停止,然而,在上下文存活期的热刷新或者停止刷新尝试的时候,只会调用销毁方法。

启动和关闭调用是很重要的。如果不同的Bean之间存在depends-on的关系的话,被依赖的一方需要更早的启动,而且关闭的更早。然而,有的时候直接的依赖是未知的,而开发者仅仅知道哪一种类型需要更早进行初始化。在这种情况下,SmartLifecycle接口定义了另一种选项,就是其父接口Phased中的getPhase()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Phased {

int getPhase();

}

public interface SmartLifecycle extends Lifecycle, Phased {

boolean isAutoStartup();

void stop(Runnable callback);

}

当启动时,拥有最低的phased的对象优先启动,而当关闭时,是相反的顺序。因此,如果一个对象实现了SmartLifecycle然后令其getPhase()方法返回了Integer.MIN_VALUE的话,就会让该对象最早启动,而最晚销毁。显然,如果getPhase()方法返回了Integer.MAX_VALUE就说明了该对象会最晚启动,而最早销毁。当考虑到使用phased的值得时候,也同时需要了解正常没有实现SmartLifecycleLifecycle对象的默认值,这个值为0。因此,任何负值将标兵对象会在标准组件启动之前启动,在标准组件销毁以后再进行销毁。

SmartLifecycle接口也定义了一个stop的回调函数。任何实现了SmartLifecycle接口的函数都必须在关闭流程完成之后调用回调中的run()方法。这样做可以是能异步关闭。而LifecycleProcessor的默认实现DefaultLifecycleProcessor会等到配置的超时时间之后再调用回调。默认的每一阶段的超时时间为30秒。开发者可以通过定义一个叫做lifecycleProcessor的Bean来覆盖默认的生命周期处理器。如果开发者需要配置超时时间,可以通过如下代码:

1
2
3
4
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>

和前文提到的,LifecycleProcessor接口定义了回调方法来刷新和关闭上下文。关闭的话,如果stop()方法已经明确调用了,那么就会驱动关闭的流程,但是如果是上下文关闭就不会发生这种情况。而刷新的回调会使能SmartLifecycle的另一个特性。当上下文刷新完毕(所有的对象已经实例化并初始化),那么就会调用回调,默认的生命周期处理器会检查每一个SmartLifecycle对象的isAutoStartup()返回的Bool值。如果为真,对象将会自动启动而不是等待明确的上下文调用,或者调用自己的start()方法(不同于上下文刷新,标准的上下文实现是不会自动启动的)。phased的值以及depends-on关系会决定对象启动和销毁的顺序。