spring bean life cycle
简介
在使用spring bean的过程中,有一个很重要的话题就是bean的生命周期。由于spring本身也是一个依赖注入的框架,它本身就包含有bean的创建和管理。而且,它也提供了很多bean管理的接口。我们在使用框架的时候该如何去选择也是一个值得探讨的话题。
Bean生命周期
粗看bean的生命周期,感觉会有点困惑,因为spring提供了很多的选项,像实现接口InitializingBean, DisposableBean,添加annotation @PostConstruct, @PreDestroy以及在bean的配置文件里设置init-method, destroy-method这些选项,都可以在实现添加定制一些用户的自定义行为。它们是有什么区别和关联呢?我们该在什么时候选择哪种实现手段呢?在讨论这些问题之前,我们先看一下它总体的生命周期。
总的来说,bean的生命周期如下图:
很明显,从上图中可以看到,bean的生命周期主要经历bean初始化以及依赖注入、检查spring awareness、调用生命周期回调方法的过程。而在bean的销毁阶段,则有调用生命周期回调方法的过程。总的来说,这些阶段并不复杂。我们针对各阶段进行讨论。为了比较和验证各阶段,我们用一个示例来贯穿整个阶段。
Bean Instantiation and DI
首先的这个阶段比较好理解,因为我们需要通过某种方式来定义目标对象和它们所依赖的对象,所以spring需要扫描这些配置文件或者java config代码。通过这种方式得到要构造对象的class信息和相关元数据信息。
在得到这些信息之后,我们需要创建一系列对象的关系图。这时候就需要调用这些对象的构造函数,然后再通过调用对一些依赖对象的属性设置方法来达到构成完整对象的过程。
假设我们有如下的类:
public class PersonBean { private static final Logger logger = LogManager.getLogger(); private String name; public PersonBean() { logger.info("Constructor of person bean is called !!"); } public String getName() { return name; } public void setName(String name) { logger.info("name property is manipulated !!"); this.name = name; } }
这个类非常简单,就是里面有一个string类型的name属性。我们在构造函数和设置name属性的方法里添加了日志记录方法。当这些方法被调用的时候,我们就可以看到输出的日志。
相对应的,我们的配置文件内容如下:
<?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="personBean" class="com.wrox.PersonBean"> <property name="name" value="Dummy Person"/> </bean> </beans>
调用的方法如下:
public class App { private static final Logger logger = LogManager.getLogger(); public static void main( String[] args ) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml"); PersonBean bean = context.getBean(PersonBean.class); logger.info("bean name is : {}", bean.getName()); context.close(); } }
如果这时候执行程序,我们会看到如下的输出结果:
2018-02-23 20:55:02,997 [main] INFO org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@51b279c9: startup date [Fri Feb 23 20:55:02 CST 2018]; root of context hierarchy 2018-02-23 20:55:03,044 [main] INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [ApplicationContext.xml] 2018-02-23 20:55:03,275 [main] INFO com.wrox.PersonBean - Constructor of person bean is called !! 2018-02-23 20:55:03,282 [main] INFO com.wrox.PersonBean - name property is manipulated !! 2018-02-23 20:55:03,314 [main] INFO com.wrox.App - bean name is : Dummy Person 2018-02-23 20:55:03,315 [main] INFO org.springframework.context.support.ClassPathXmlApplicationContext - Closing org.springframework.context.support.ClassPathXmlApplicationContext@51b279c9: startup date [Fri Feb 23 20:55:02 CST 2018]; root of context hierarchy
很显然,除了前面spring自带的log输出,主要的调用过程就是首先调用PeronBean的构造函数,然后调用设置属性name的方法。
check for spring awareness
spring提供了一组仅用于spring内部使用的一组接口。一般来说,对于通用的依赖注入来说,我们希望尽量保证依赖注入功能的通用性和可移植性。对于spring awareness的这一组接口来说,它主要提供一些方法使得被创建的bean能够和创建bean的容器之间有一定的交互。比如BeanNameAware接口。我们可以通过实现它得到当前被构建的bean的名字。并根据这些信息实现一些特定的业务逻辑。
在这里,我们的示例很简单,只需要添加一个对BeanNameAware接口的实现:
public class PersonBean implements BeanNameAware
我们实现的这个方法细节如下:
@Override public void setBeanName(String name) { logger.info("Bean with name {} get set", name); }
在xml配置文件不变的情况下,我们运行程序将得到如下的输出:
2018-02-23 21:04:40,636 [main] INFO org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@51b279c9: startup date [Fri Feb 23 21:04:40 CST 2018]; root of context hierarchy 2018-02-23 21:04:40,680 [main] INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [ApplicationContext.xml] 2018-02-23 21:04:40,908 [main] INFO com.wrox.PersonBean - Constructor of person bean is called !! 2018-02-23 21:04:40,915 [main] INFO com.wrox.PersonBean - name property is manipulated !! 2018-02-23 21:04:40,916 [main] INFO com.wrox.PersonBean - Bean with name personBean get set 2018-02-23 21:04:40,946 [main] INFO com.wrox.App - bean name is : Dummy Person 2018-02-23 21:04:40,947 [main] INFO org.springframework.context.support.ClassPathXmlApplicationContext - Closing org.springframework.context.support.ClassPathXmlApplicationContext@51b279c9: startup date [Fri Feb 23 21:04:40 CST 2018]; root of context hierarchy
可见,这个方法的调用过程是在对象创建好并设置好属性之后发生的。
除了上述的这个接口,spring还有一系列的awareness接口,像BeanClassLoaderAware, BeanFactoryAware, EnvironmentAware, EmbeddedValueResolverAware, ResourceLoaderAware, ApplicationEventPublisherAware, MessageSourceAware, ApplicationContextAware。实现这些接口的bean会在创建的过程中被调用。
Bean Life-cycle callback
InitializingBean
最早出现在spring里的一个生命周期中应用的回调方法大概就是InitializingBean接口方法了。如果在bean创建后我们需要一些自定义的构造行为,需要这个被构造的bean实现这个接口。并在接口的实现方法里进行具体的自定义行为。比如说,在示例里我们增加实现这个接口:
public class PersonBean implements BeanNameAware, InitializingBean
我们添加方法的实现如下:
@Override public void afterPropertiesSet() throws Exception { logger.info("afterPropertiesSet method of person bean is called !!"); }
运行程序将得到如下的输出:
2018-02-23 21:27:02,077 [main] INFO org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@51b279c9: startup date [Fri Feb 23 21:27:02 CST 2018]; root of context hierarchy 2018-02-23 21:27:02,121 [main] INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [ApplicationContext.xml] 2018-02-23 21:27:02,351 [main] INFO com.wrox.PersonBean - Constructor of person bean is called !! 2018-02-23 21:27:02,359 [main] INFO com.wrox.PersonBean - name property is manipulated !! 2018-02-23 21:27:02,360 [main] INFO com.wrox.PersonBean - Bean with name personBean get set 2018-02-23 21:27:02,360 [main] INFO com.wrox.PersonBean - afterPropertiesSet method of person bean is called !! 2018-02-23 21:27:02,390 [main] INFO com.wrox.App - bean name is : Dummy Person 2018-02-23 21:27:02,391 [main] INFO org.springframework.context.support.ClassPathXmlApplicationContext - Closing org.springframework.context.support.ClassPathXmlApplicationContext@51b279c9: startup date [Fri Feb 23 21:27:02 CST 2018]; root of context hierarchy
很明显,这部分的方法将在我们构造好bean之后调用,但是在几个方法中比较靠后的地方了。
init-method
还有一种方法,相对来说对spring本身的依赖更低一些。毕竟前一种方法的实现需要实现一个spring特定的接口。而这种方法并不需要,我们只需要在配置里指定这个初始化的方法。我们需要做两个修改,首先在配置文件里设定执行具体初始化的方法:
<bean id="personBean" class="com.wrox.PersonBean" init-method="init"> <property name="name" value="Dummy Person"/> </bean>
然后我们在具体的PersonBean里实现init方法:
public void init() { logger.info("init bean for PersonBean"); }
我们再执行程序,会发现有如下的输出结果:
2018-02-23 21:34:03,327 [main] INFO org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@51b279c9: startup date [Fri Feb 23 21:34:03 CST 2018]; root of context hierarchy 2018-02-23 21:34:03,375 [main] INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [ApplicationContext.xml] 2018-02-23 21:34:03,602 [main] INFO com.wrox.PersonBean - Constructor of person bean is called !! 2018-02-23 21:34:03,612 [main] INFO com.wrox.PersonBean - name property is manipulated !! 2018-02-23 21:34:03,614 [main] INFO com.wrox.PersonBean - Bean with name personBean get set 2018-02-23 21:34:03,614 [main] INFO com.wrox.PersonBean - afterPropertiesSet method of person bean is called !! 2018-02-23 21:34:03,615 [main] INFO com.wrox.PersonBean - init bean for PersonBean 2018-02-23 21:34:03,649 [main] INFO com.wrox.App - bean name is : Dummy Person 2018-02-23 21:34:03,649 [main] INFO org.springframework.context.support.ClassPathXmlApplicationContext - Closing org.springframework.context.support.ClassPathXmlApplicationContext@51b279c9: startup date [Fri Feb 23 21:34:03 CST 2018]; root of context hierarchy
和前面实现接口的比起来,它执行的顺序更加靠后一点。
@PostConstruct annotation
和前面两种方法比起来,这种基于annotation的方法更加新一些。它是基于JSR250规范的一种实现。所有支持这种规范的容器都可以兼容它。这种实现的过程显得比较简洁。它需要在配置文件里添加对annotation的支持:
<context:annotation-config />
然后我们定义一个用@PostConstruct修饰的方法:
@PostConstruct public void postConstruct() { logger.info("postconstruct annotation"); }
这时候程序的输出结果将如下:
2018-02-23 21:39:46,438 [main] INFO org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@51b279c9: startup date [Fri Feb 23 21:39:46 CST 2018]; root of context hierarchy 2018-02-23 21:39:46,481 [main] INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [ApplicationContext.xml] 2018-02-23 21:39:46,711 [main] INFO com.wrox.PersonBean - Constructor of person bean is called !! 2018-02-23 21:39:46,720 [main] INFO com.wrox.PersonBean - name property is manipulated !! 2018-02-23 21:39:46,721 [main] INFO com.wrox.PersonBean - Bean with name personBean get set 2018-02-23 21:39:46,721 [main] INFO com.wrox.PersonBean - postconstruct annotation 2018-02-23 21:39:46,721 [main] INFO com.wrox.PersonBean - afterPropertiesSet method of person bean is called !! 2018-02-23 21:39:46,722 [main] INFO com.wrox.PersonBean - init bean for PersonBean 2018-02-23 21:39:46,751 [main] INFO com.wrox.App - bean name is : Dummy Person 2018-02-23 21:39:46,751 [main] INFO org.springframework.context.support.ClassPathXmlApplicationContext - Closing org.springframework.context.support.ClassPathXmlApplicationContext@51b279c9: startup date [Fri Feb 23 21:39:46 CST 2018]; root of context hierarchy
可见基于这种方式的执行方法会比前两种方式要优先级高一点。
@PostConstruct和BeanPostProcessor的辨析
在前面的几种方法里,还有一个比较有意思的hook。就是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; }
这两个方法是它的默认实现,这里并没有做任何的变动,而只是直接返回bean对象本身。我们在具体的应用里可以通过实现这个接口来添加自定义的行为。
当然,这个接口有一个特殊的地方就是,我们所有添加的自定义行为将被应用到所有创建的bean上,而不仅仅是单独的一个bean里。
我们来看一个具体的示例。在我们前面的示例里,我们添加如下的BeanPostProcessor实现:
public class CustomizedBeanPostProcessor implements BeanPostProcessor { private static final Logger logger = LogManager.getLogger(); @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { logger.info("Post process before initialization for bean {} with name {}", bean, beanName); return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName); } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { logger.info("Post process after initialization for bean {} with name {}", bean, beanName); return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName); } }
为了让这个实现生效,我们需要在配置文件里声明这个实现:
<bean class="com.wrox.CustomizedBeanPostProcessor"/>
这时候如果运行程序,我们将看到如下的输出:
2018-02-25 12:08:58,811 [main] INFO org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@51b279c9: startup date [Sun Feb 25 12:08:58 CST 2018]; root of context hierarchy 2018-02-25 12:08:58,855 [main] INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [ApplicationContext.xml] 2018-02-25 12:08:59,093 [main] INFO com.wrox.CustomizedBeanPostProcessor - Post process before initialization for bean org.springframework.context.event.EventListenerMethodProcessor@23348b5d with name org.springframework.context.event.internalEventListenerProcessor 2018-02-25 12:08:59,093 [main] INFO com.wrox.CustomizedBeanPostProcessor - Post process after initialization for bean org.springframework.context.event.EventListenerMethodProcessor@23348b5d with name org.springframework.context.event.internalEventListenerProcessor 2018-02-25 12:08:59,095 [main] INFO com.wrox.CustomizedBeanPostProcessor - Post process before initialization for bean org.springframework.context.event.DefaultEventListenerFactory@37ceb1df with name org.springframework.context.event.internalEventListenerFactory 2018-02-25 12:08:59,095 [main] INFO com.wrox.CustomizedBeanPostProcessor - Post process after initialization for bean org.springframework.context.event.DefaultEventListenerFactory@37ceb1df with name org.springframework.context.event.internalEventListenerFactory 2018-02-25 12:08:59,096 [main] INFO com.wrox.PersonBean - Constructor of person bean is called !! 2018-02-25 12:08:59,107 [main] INFO com.wrox.PersonBean - name property is manipulated !! 2018-02-25 12:08:59,108 [main] INFO com.wrox.PersonBean - Bean with name personBean get set 2018-02-25 12:08:59,108 [main] INFO com.wrox.CustomizedBeanPostProcessor - Post process before initialization for bean com.wrox.PersonBean@5fbe4146 with name personBean 2018-02-25 12:08:59,108 [main] INFO com.wrox.PersonBean - postconstruct annotation 2018-02-25 12:08:59,108 [main] INFO com.wrox.PersonBean - afterPropertiesSet method of person bean is called !! 2018-02-25 12:08:59,108 [main] INFO com.wrox.PersonBean - init bean for PersonBean 2018-02-25 12:08:59,109 [main] INFO com.wrox.CustomizedBeanPostProcessor - Post process after initialization for bean com.wrox.PersonBean@5fbe4146 with name personBean 2018-02-25 12:08:59,146 [main] INFO com.wrox.App - bean name is : Dummy Person 2018-02-25 12:08:59,146 [main] INFO org.springframework.context.support.ClassPathXmlApplicationContext - Closing org.springframework.context.support.ClassPathXmlApplicationContext@51b279c9: startup date [Sun Feb 25 12:08:58 CST 2018]; root of context hierarchy
我们看里面CustomizedBeanProcessor的日志输出,会发现它相关联的输出有几个,即有PersonBean相关的,也有两个额外的。这些也是spring内部创建的对象,只是我们的应用里不会直接使用。
这里,还有一个让我们有点疑惑的地方,就是BeanPostProcessor接口和@PostConstruct的关系。BeanPostProcessor是一个spring框架提供给用户用来扩展特定集成实现的接口。它的方法被调用的时候,一般都是在对象已经被创建好之后,而且依赖已经注入到里面了。在一般的情况下我们会用的很少。而@PostConstruct相当于实现里面postProcessAfterInitialization方法的简便手段。
Bean destruction life-cycle
spring在bean的生命周期里也对bean的解构提供了一些支持。比如@PreDestroy annotation。这个方法的实现也和前面@PostConstruct的实现差不多,就是在某个方法前添加这个annotation就可以了。
比如我们在示例里添加代码如下:
@PreDestroy public void preDestroy() { logger.info("predestroy annotation"); }
如果运行代码,会发现有如下更多的日志输出:
2018-02-25 12:33:09,195 [main] INFO com.wrox.PersonBean - predestroy annotation
还有一个实现对象析构的接口,就是DisposableBean。我们可以尝试实现这个接口:
public class PersonBean implements BeanNameAware, InitializingBean, DisposableBean
具体实现的方法如下:
@Override public void destroy() throws Exception { logger.info("destroy method of person bean is called !!"); }
再运行程序,将看到有如下额外的输出:
2018-02-25 12:39:28,519 [main] INFO com.wrox.PersonBean - predestroy annotation 2018-02-25 12:39:28,519 [main] INFO com.wrox.PersonBean - destroy method of person bean is called !!
可以看到这里destroy方法的执行是在@PreDestroy标注的方法之后。
还有一个我们可以利用的方法就是在配置文件里指定destroy-method。比如说我们将前面PersonBean的配置修改成如下:
<bean id="personBean" class="com.wrox.PersonBean" init-method="init" destroy-method="destroyBean"> <property name="name" value="Dummy Person"/> </bean>
这个标注的destroy-method实现如下:
public void destroyBean() { logger.info("destroy bean for PersonBean"); }
运行程序时,destroy bean对象的输出如下:
2018-02-25 13:29:07,298 [main] INFO com.wrox.PersonBean - predestroy annotation 2018-02-25 13:29:07,298 [main] INFO com.wrox.PersonBean - destroy method of person bean is called !! 2018-02-25 13:29:07,298 [main] INFO com.wrox.PersonBean - destroy bean for PersonBean
当然,如果我们要让这些destroy方法生效的话,我们需要在程序里显式的调用contxt.close方法。
各种选项的取舍
总的来说,我们在bean创建的生命周期中,默认的基本步骤是调用对象的构造函数和设置属性的方法。在必要的时候,我们可以选择实现InitializeBean接口,定义init-method方法或者使用@PostConstruct标注。一般来说,我们没必要把每种方法都用到实际的项目里。在一些对项目工程的移植性有要求的地方,使用init-method配置或者@PostConstruct的方法会更加理想一些。而在一些对移植性没什么要求却希望对象能支持自定义行为的话,使用InitializeBean接口也是一个不错的选择。因为必须要实现一个接口,它使得我们不会忘记定义这个过程。
在某些特殊的情况下,如果我们希望对所有创建的bean对象做一些自定义的操作或者定制的话,一种比较好的手段就是实现BeanPostProcessor接口。
参考材料
https://www.journaldev.com/2637/spring-bean-life-cycle
https://javabeat.net/life-cycle-management-of-a-spring-bean/
http://wideskills.com/spring/spring-bean-lifecycle
https://howtodoinjava.com/spring/spring-core/spring-bean-life-cycle/
https://stackoverflow.com/questions/29743320/how-exactly-works-the-spring-bean-post-processor
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans