Spring 容器中的 Bean 是有生命周期的,Spring 允许在 Bean 在初始化完成后以及 Bean 销毁前执行特定的操作,常用的设定方式有以下三种:

  • 通过实现 InitializingBean/DisposableBean 接口来定制初始化之后 / 销毁之前的操作方法;
  • 通过 元素的 init-method/destroy-method 属性指定初始化之后 / 销毁之前调用的操作方法;
  • 在指定方法上加上 @PostConstruct 或 @PreDestroy 注解来制定该方法是在初始化之后还是销毁之前调用。

这是我们就有个疑问,这三种方式是完全等同的吗,孰先孰后?

下面我们将带着这个疑问,试图通过测试代码以及分析 Spring 源码找到答案。

首先,我们还是编写一个简单的测试代码:

Java 代码

  1. public class InitSequenceBean implements InitializingBean {
  2. public InitSequenceBean() {
  3. System.out.println("InitSequenceBean: constructor");
  4. }
  5. @PostConstruct
  6. public void postConstruct() {
  7. System.out.println("InitSequenceBean: postConstruct");
  8. }
  9. public void initMethod() {
  10. System.out.println("InitSequenceBean: init-method");
  11. }
  12. @Override
  13. public void afterPropertiesSet() throws Exception {
  14. System.out.println("InitSequenceBean: afterPropertiesSet");
  15. }
  16. }

并且在配置文件中添加如下 Bean 定义:

<bean class="InitSequenceBean" init-method="initMethod">bean>

好了,我们启动 Spring 容器,观察输出结果,就可知道三者的先后顺序了:

|

InitSequenceBean: constructor

InitSequenceBean: postConstruct

InitSequenceBean: afterPropertiesSet

InitSequenceBean: init-method

|

通过上述输出结果,三者的先后顺序也就一目了然了:

Constructor > @PostConstruct > InitializingBean > init-method


先大致分析下为什么会出现这些的结果:构造器(Constructor)被率先调用毋庸置疑,InitializingBean 先于 init-method 我们也可以理解(在也谈 Spring 容器的生命周期中已经讨论过),但是 PostConstruct 为何率先于 InitializingBean 执行呢?

我们再次带着这个疑问去查看 Spring 源代码来一探究竟。

通过 Debug 并查看调用栈,我们发现了这个类 org.springframework.context.annotation.CommonAnnotationBeanPostProcessor,从命名上,我们就可以得到某些信息——这是一个 BeanPostProcessor。想到了什么?在也谈 Spring 容器的生命周期中,我们提到过 BeanPostProcessor 的 postProcessBeforeInitialization 是在 Bean 生命周期中 afterPropertiesSet 和 init-method 之前执被调用的。

再次观察 CommonAnnotationBeanPostProcessor 这个类,它继承自 InitDestroyAnnotationBeanPostProcessor。InitDestroyAnnotationBeanPostProcessor 顾名思义,就是在 Bean 初始化和销毁的时候所作的一个前置 / 后置处理器。

通过查看 InitDestroyAnnotationBeanPostProcessor 类下的 postProcessBeforeInitialization 方法:

Java 代码

  1. public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  2. LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
  3. try {
  4. metadata.invokeInitMethods(bean, beanName);
  5. }
  6. catch (InvocationTargetException ex) {
  7. throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException());
  8. }
  9. catch (Throwable ex) {
  10. throw new BeanCreationException(beanName, "Couldn't invoke init method", ex);
  11. }
  12. return bean;
  13. }

查看 findLifecycleMetadata 方法,继而我们跟踪到 buildLifecycleMetadata 这个方法体中,看下 buildLifecycleMetadata 这个方法体的内容:

Java 代码

  1. private LifecycleMetadata buildLifecycleMetadata(final Class clazz) {
  2. final LifecycleMetadata newMetadata = new LifecycleMetadata();
  3. final boolean debug = logger.isDebugEnabled();
  4. ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {
  5. public void doWith(Method method) {
  6. if (initAnnotationType != null) {
  7. if (method.getAnnotation(initAnnotationType) != null) {
  8. newMetadata.addInitMethod(method);
  9. if (debug) {
  10. logger.debug("Found init method on class [" + clazz.getName() + "]:" + method);
  11. }
  12. }
  13. }
  14. if (destroyAnnotationType != null) {
  15. if (method.getAnnotation(destroyAnnotationType) != null) {
  16. newMetadata.addDestroyMethod(method);
  17. if (debug) {
  18. logger.debug("Found destroy method on class [" + clazz.getName() + "]:" + method);
  19. }
  20. }
  21. }
  22. }
  23. });
  24. return newMetadata;
  25. }

分析这段代码发现,在这里会去判断某方法有没有被 initAnnotationType/destroyAnnotationType 注释,如果有,则添加到 init/destroy 队列中,后续一一执行。

initAnnotationType/destroyAnnotationType 注释是什么呢,我们在 CommonAnnotationBeanPostProcessor 的构造函数中看到下面这段代码:

Java 代码

  1. public CommonAnnotationBeanPostProcessor() {
  2. setOrder(Ordered.LOWEST_PRECEDENCE - 3);
  3. setInitAnnotationType(PostConstruct.class);
  4. setDestroyAnnotationType(PreDestroy.class);
  5. ignoreResourceType("javax.xml.ws.WebServiceContext");
  6. }

一切都清晰了吧。一言以蔽之,@PostConstruct 注解后的方法在 BeanPostProcessor 前置处理器中就被执行了,所以当然要先于 InitializingBean 和 init-method 执行了。

最后,给出本文的结论,Bean 在实例化的过程中:

Constructor > @PostConstruct > InitializingBean > init-method

  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:PipeSoloSymWide 等,欢迎大家加入,贡献开源。

    3132 引用 • 3895 回帖 • 654 关注
  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    488 引用 • 1061 回帖 • 996 关注
感谢    关注    收藏    赞同    反对    举报    分享