关于@PostConstruct、afterPropertiesSet和init-method的执行顺序

目录
  • @PostConstruct、init-method、afterPropertiesSet() 执行顺序
  • @PostConstruct 标注的方法在何时被谁调用
  • init-method、afterPropertiesSet() 的调用
  • 顺序的确定

@PostConstruct、init-method、afterPropertiesSet() 执行顺序

想要知道 @PostConstruct、init-method、afterPropertiesSet() 的执行顺序,只要搞明白它们各自在什么时候被谁调用就行了。

程序版本:Spring Boot 2.3.5.RELEASE

准备好要验证的材料:

public class Foo implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet()");
    }
    @PostConstruct
    public void init() {
        System.out.println("@PostConstruct");
    }
    private void initMethod() {
        System.out.println("initMethod()");
    }
}
@Configuration
public class FooConfiguration {
    @Bean(initMethod = "initMethod")
    public Foo foo() {
        return new Foo();
    }
}

执行启动类,可以看到在控制台中输出:

@PostConstruct

afterPropertiesSet()

initMethod()

说明执行顺序是:@PostConstruct、afterPropertiesSet()、init-method

接下来将跟着源码来了解为什么是这个顺序。

@PostConstruct 标注的方法在何时被谁调用

首先,在 init() 中打个断点,然后以 debug 的方式启动项目,得到下面的调用栈:

init:23, Foo (com.xurk.init.foo)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invoke:389, InitDestroyAnnotationBeanPostProcessor$LifecycleElement (org.springframework.beans.factory.annotation)
invokeInitMethods:333, InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata (org.springframework.beans.factory.annotation)
postProcessBeforeInitialization:157, InitDestroyAnnotationBeanPostProcessor (org.springframework.beans.factory.annotation)
applyBeanPostProcessorsBeforeInitialization:415, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
initializeBean:1786, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:594, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:516, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:324, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, 2103763750 (org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$169)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:322, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:202, AbstractBeanFactory (org.springframework.beans.factory.support)
preInstantiateSingletons:897, DefaultListableBeanFactory (org.springframework.beans.factory.support)
finishBeanFactoryInitialization:879, AbstractApplicationContext (org.springframework.context.support)
refresh:551, AbstractApplicationContext (org.springframework.context.support)
refresh:143, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:758, SpringApplication (org.springframework.boot)
refresh:750, SpringApplication (org.springframework.boot)
refreshContext:405, SpringApplication (org.springframework.boot)
run:315, SpringApplication (org.springframework.boot)
run:1237, SpringApplication (org.springframework.boot)
run:1226, SpringApplication (org.springframework.boot)
main:14, InitApplication (com.xurk.init)

从上往下看,跳过使用 sun.reflect 的方法,进入到第6行。

public void invoke(Object target) throws Throwable {
   ReflectionUtils.makeAccessible(this.method);
   this.method.invoke(target, (Object[]) null);
}

很明显,这里是在通过反射调用某个对象的一个方法,并且这个“某个对象”就是我们定义的 Foo的实例对象了。

那么这里的 method 又是在什么时候进行赋值的呢?

invoke(...) 全路径是:

org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.LifecycleElement#invoke

并且 LifecycleElement 有且只有一个显示声明并且带参数的构造器,这个要传入构造器的参数正是 invoke(...) 使用的那个 Method 对象。

接下来就是查一下,是谁在 new LifecycleElement 。

于是定位到:

org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor#buildLifecycleMetadata

private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) {
    if (!AnnotationUtils.isCandidateClass(clazz, Arrays.asList(this.initAnnotationType, this.destroyAnnotationType))) {
        return this.emptyLifecycleMetadata;
    }
    List<LifecycleElement> initMethods = new ArrayList<>();
    List<LifecycleElement> destroyMethods = new ArrayList<>();
    Class<?> targetClass = clazz;
    do {
        final List<LifecycleElement> currInitMethods = new ArrayList<>();
        final List<LifecycleElement> currDestroyMethods = new ArrayList<>();
        ReflectionUtils.doWithLocalMethods(targetClass, method -> {
            if (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) {
                LifecycleElement element = new LifecycleElement(method);
                currInitMethods.add(element);
                if (logger.isTraceEnabled()) {
                    logger.trace("Found init method on class [" + clazz.getName() + "]: " + method);
                }
            }
            if (this.destroyAnnotationType != null && method.isAnnotationPresent(this.destroyAnnotationType)) {
                currDestroyMethods.add(new LifecycleElement(method));
                if (logger.isTraceEnabled()) {
                    logger.trace("Found destroy method on class [" + clazz.getName() + "]: " + method);
                }
            }
        });
        initMethods.addAll(0, currInitMethods);
        destroyMethods.addAll(currDestroyMethods);
        targetClass = targetClass.getSuperclass();
    }
    while (targetClass != null && targetClass != Object.class);
    return (initMethods.isEmpty() && destroyMethods.isEmpty() ? this.emptyLifecycleMetadata :
            new LifecycleMetadata(clazz, initMethods, destroyMethods));
}

第15-26行是在通过反射判断方法上是否存在某个注解,如果是的话就加到一个集合中,在最后用于构建 LifecycleMetadata 实例。

在这里因为我们要查找的是被赋值的内容,所以在使用IDE进行查找时只要关注 write 相关的内容就行了。

到这里,已经知道了是在 CommonAnnotationBeanPostProcessor 的构造器中进行 set 的,也就是当创建 CommonAnnotationBeanPostProcessor 实例的时候就会进行赋值,并且 CommonAnnotationBeanPostProcessor 是 InitDestroyAnnotationBeanPostProcessor 的子类。

并且,CommonAnnotationBeanPostProcessor 构造器中调用的 setInitAnnotationType 其实是它父类的方法,实际是对 InitDestroyAnnotationBeanPostProcessor 的实例的 initAnnotationType 字段进行赋值。

到这里已经可以明确 buildLifecycleMetadata(...) 中判断的正是 @PostConstruct。

再回到buildLifecycleMetadata(...) ,查看其使用的 doWithLocalMethods(...) 的实现。

public static void doWithLocalMethods(Class<?> clazz, MethodCallback mc) {
    Method[] methods = getDeclaredMethods(clazz, false);
    for (Method method : methods) {
        try {
            mc.doWith(method);
        }
        catch (IllegalAccessException ex) {
            throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex);
        }
    }
}

很简单,通过反射获得类的所有方法,然后调用一个函数接口的方法,这个函数接口的实现就是判断方法是不是被 @PostConstruct 标注,如果被标注的话放到一个名字叫 currInitMethods 的集合中。

看到这里或许你已经意识到了, @PostConstruct 可以标注多个方法,并且因为反射获取方法时是根据声明顺序的、 currInitMethods 是 ArrayList,两者之间的顺序是一样的。

好了,被 @PostConstruct 标注的方法已经找到放到集合中了,将被用来构建 LifecycleMetadata 实例了。

buildLifecycleMetadata(...) 方法返回一个 LifecycleMetadata 实例,这个返回值中包含传入Class实例中得到的所有被 @PostConstruct 标注的 Method 实例,接下来要看看是谁在调用 buildLifecycleMetadata(...) 方法,看看它是怎么用的?

追溯到 findLifecycleMetadata(...) 而 findLifecycleMetadata(...) 又有好几处被调用。

查看最早得到的方法调用栈,查到postProcessBeforeInitializatio(...) ,它又是再被谁调用?

init:23, Foo (com.xurk.init.foo)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invoke:389, InitDestroyAnnotationBeanPostProcessor$LifecycleElement (org.springframework.beans.factory.annotation)
invokeInitMethods:333, InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata (org.springframework.beans.factory.annotation)
postProcessBeforeInitialization:157, InitDestroyAnnotationBeanPostProcessor (org.springframework.beans.factory.annotation)
applyBeanPostProcessorsBeforeInitialization:415, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
initializeBean:1786, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)

跟着方法调用栈,我们来到

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization

@Override
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
      throws BeansException {
   Object result = existingBean;
   for (BeanPostProcessor processor : getBeanPostProcessors()) {
      Object current = processor.postProcessBeforeInitialization(result, beanName);
      if (current == null) {
         return result;
      }
      result = current;
   }
   return result;
}

在这个方法,遍历 BeanPostProcessors 集合并执行每个 BeanPostProcessor 的 postProcessBeforeInitialization(...) 方法。

**那么 getBeanPostProcessors() 中的内容又是在什么时候放进去的呢?都有哪些内容?**可以通过 AnnotationConfigUtils和 CommonAnnotationBeanPostProcessor 查找,这里就不再赘述了。

查找 applyBeanPostProcessorsBeforeInitialization(...) 的调用者,来到

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
   if (System.getSecurityManager() != null) {
      AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
         invokeAwareMethods(beanName, bean);
         return null;
      }, getAccessControlContext());
   }
   else {
      invokeAwareMethods(beanName, bean);
   }
   Object wrappedBean = bean;
   if (mbd == null || !mbd.isSynthetic()) {
      wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
   }
   try {
      invokeInitMethods(beanName, wrappedBean, mbd);
   }
   catch (Throwable ex) {
      throw new BeanCreationException(
            (mbd != null ? mbd.getResourceDescription() : null),
            beanName, "Invocation of init method failed", ex);
   }
   if (mbd == null || !mbd.isSynthetic()) {
      wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
   }
   return wrappedBean;
}

顾名思义,在这个方法中对 Bean 进行初始化。是在被注入前一定要经过的过程,到这里被 @PostConstruct 标注的方法执行已经完成了。至于这个方法谁调用可以自己查看调用栈。

init-method、afterPropertiesSet() 的调用

细心的朋友可能已经发现了在 initializeBean(...) 中有调用到一个叫做 invokeInitMethods(...) 的方法。

protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd)
      throws Throwable {
   boolean isInitializingBean = (bean instanceof InitializingBean);
   if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
      if (logger.isTraceEnabled()) {
         logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
      }
      if (System.getSecurityManager() != null) {
         try {
            AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
               ((InitializingBean) bean).afterPropertiesSet();
               return null;
            }, getAccessControlContext());
         }
         catch (PrivilegedActionException pae) {
            throw pae.getException();
         }
      }
      else {
         ((InitializingBean) bean).afterPropertiesSet();
      }
   }
   if (mbd != null && bean.getClass() != NullBean.class) {
      String initMethodName = mbd.getInitMethodName();
      if (StringUtils.hasLength(initMethodName) &&
            !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
            !mbd.isExternallyManagedInitMethod(initMethodName)) {
         invokeCustomInitMethod(beanName, bean, mbd);
      }
   }
}

BeanDefinition是Bean定义的一个抽象。类似于在Java中存在Class类用于描述一个类,里面有你定义的 Bean 的各种信息。

在第4行,判断是不是 InitializingBean,如果是的话会进行类型强转,然后调用 afterPropertiesSet()。

在第26行,获得到自定义初始化方法的名字,然后在第30行调用 invokeCustomInitMethod 执行完成。

顺序的确定

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
   if (System.getSecurityManager() != null) {
      AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
         invokeAwareMethods(beanName, bean);
         return null;
      }, getAccessControlContext());
   }
   else {
      invokeAwareMethods(beanName, bean);
   }
   Object wrappedBean = bean;
   if (mbd == null || !mbd.isSynthetic()) {
      wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
   }
   try {
      invokeInitMethods(beanName, wrappedBean, mbd);
   }
   catch (Throwable ex) {
      throw new BeanCreationException(
            (mbd != null ? mbd.getResourceDescription() : null),
            beanName, "Invocation of init method failed", ex);
   }
   if (mbd == null || !mbd.isSynthetic()) {
      wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
   }
   return wrappedBean;
}

initializeBean(...) 方法中,先执行 applyBeanPostProcessorsBeforeInitialization(...) 在执行 invokeInitMethods(...) 。

而 applyBeanPostProcessorsBeforeInitialization(...) 会执行被 @PostConstruct 标注的方法,invokeInitMethods(...) 会执行 afterPropertiesSet() 和自定义的初始化方法,并且 afterPropertiesSet() 在自定义的初始化方法之前执行。

所以它们之间的执行顺序是:

@PostConstruct > afterPropertiesSet() > initMethod()

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

时间: 2021-09-15

SpringBoot @PostConstruct原理用法解析

前言 本节我们将学习一下@PostConstruct的用法. 概述 @PostContruct是spring框架的注解,在方法上加该注解会在项目启动的时候执行该方法,也可以理解为在spring容器初始化的时候执行该方法. /** * 项目启动时,初始化定时器 */ @PostConstruct public void init() { List<Job> jobList = jobDao.selectJobAll(); for (Job job : jobList) { CronTrigger

Spring定时任务中@PostConstruct被多次执行异常的分析与解决

发现问题 最近在项目中刚刚修改一个功能,代码正准备验证,启动Idea的debug模式,运行项目,发现启动失败,查看日志发现定时任务被重复执行,导致异常.debug定时任务的初始化类,发现启动定时任务是在@PostConstruct方法中执行的,网上查询,有说Spring在某种情况下初始化有bug,注解@Component可能出现多次执行.把@Component修改为@Service就行了! 结果我改了也没解决问题! 无赖更一步跟进日志,发现以下内容: [ERROR][2017-06-20 19:

@PostConstruct在项目启动时被执行两次或多次的原因及分析

@PostConstruct项目启动时被执行两次或多次 原因 是因为文件对@PostConstruct所在类扫描了两次! 首先排查,带有扫描包配置(context:component-scan)的同一spring文件,是否在web.xml配置中,初始化就执行的那种配置(比如context-param,init-param),被重复的配置了两遍. 然后在排查,web.xml中配置了初始化配置的多个spring文件是否都扫描了@PostConstruct所在类的所在包!常见SpringMVC文件的扫

SpringBoot中的Bean的初始化与销毁顺序解析

我今天学习到SpringBoot里面自定义Bean的初始化与销毁方法 我先总结一下我学到的四种方法: 方法一: 指定init-method 和 destory-method 方法二: 通过让 Bean 实现 InitializingBean 接口,定义初始化逻辑 DisposableBean 接口,定义销毁逻辑 方法三: 用 @PostConstruct,在 Bean 创建完成并且赋值完成后,执行该注解标注的方法 @PreDestroy,在容器销毁 Bean 之前,执行该注解标注的方法 方法四:

Spring Bean的初始化和销毁实例详解

本文实例讲述了Spring Bean的初始化和销毁.分享给大家供大家参考,具体如下: 一 点睛 在开发过程中,经常遇到在Bean使用之前或者之后做一些必要的操作,Spring对Bean的生命周期的操作提供了支持. 1 Java配置方式:使用@Bean的initMethod和destroyMethod. 2 注解方式:利用JSR-250的@PostConstruct和@PreDestroy. 二 实战 1 增加JSR250支持 <dependency> <groupId>javax.

web.xml中servlet, bean, filter, listenr 加载顺序_动力节点Java学院整理

web.xml 文件中一般包括 servlet, spring, filter, listenr的配置.那么他们是按照一个什么顺序加载呢?加载顺序会影响对spring bean 的调用. 比如filter需要用到 bean ,但是加载顺序是 先加载filter 后加载spring,则filter中初始化操作中的bean为null:首先可以肯定 加载顺序与他们在web.xml 文件中的先后顺序无关. web.xml 中 listener 和 serverlet 的加载顺序为 先 listener

springboot中自定义异常以及定制异常界面实现过程解析

不多说废话,直接进入主菜!! 步骤: 1.搭建SpringBoot的开发环境,略(有不会的可以私信我). 2.编写一个自定义异常,自定义异常需要继承RuntimeException.写一个构造函数,并调用父类保存异常信息. public class MyException extends RuntimeException { public MyException(String massage) { super(massage); } } 3.编写一个控制器,用于抛出异常.当请求参数param=a

如何正确控制springboot中bean的加载顺序小结篇

1.为什么需要控制加载顺序 springboot遵从约定大于配置的原则,极大程度的解决了配置繁琐的问题.在此基础上,又提供了spi机制,用spring.factories可以完成一个小组件的自动装配功能. 在一般业务场景,可能你不大关心一个bean是如何被注册进spring容器的.只需要把需要注册进容器的bean声明为@Component即可,spring会自动扫描到这个Bean完成初始化并加载到spring上下文容器. 而当你在项目启动时需要提前做一个业务的初始化工作时,或者你正在开发某个中间

SPRING FRAMEWORK BEAN作用域和生命周期原理解析

这篇文章主要介绍了SPRING FRAMEWORK BEAN作用域和生命周期原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 Spring beand的作用域 设置为singleton时,相当于一个类只能有一个实例,当再次申请时,返回的是同一个实例 可以看到两个bean实例的hashcode值是一样的,说明在此申请到的是同一个实例 将bean的作用域设置为prototype时,再次运行,可以看到,申请到的是两个不同bean实例 目前只学习

Spring Bean初始化及销毁多种实现方式

这篇文章主要介绍了Spring Bean初始化及销毁多种实现方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.前言 日常开发过程有时需要在应用启动之后加载某些资源,或者在应用关闭之前释放资源.Spring 框架提供相关功能,围绕 Spring Bean 生命周期,可以在 Bean 创建过程初始化资源,以及销毁 Bean 过程释放资源.Spring 提供多种不同的方式初始化/销毁 Bean,如果同时使用这几种方式,Spring 如何处理这几

浅谈spring容器中bean的初始化

当我们在spring容器中添加一个bean时,如果没有指明它的scope属性,则默认是singleton,也就是单例的. 例如先声明一个bean: public class People { private String name; private String sex; public String getName() { return name; } public void setName(String name) { this.name = name; } public String get

详解SpringBoot中Session超时原理说明

一:前言: 最近支付后台登录一段时间后如果没有任何操作,总是需要重新登录才可以继续访问页面,出现这个问题的原因就是session超时,debug代码后发现session的超时时间是1800s.也就是说当1800秒内没有任何操作,session就会出现超时现象.那这个超时时间是如何设置的呢?然后该如何重新设置此超时时间呢?系统又如何判断session超时的呢?接下来就一一进行解答. 二:系统session超时时间如何默认的? 说明:获取session超时时间的方法为"request.getSess

springboot中使用自定义两级缓存的方法

工作中用到了springboot的缓存,使用起来挺方便的,直接引入redis或者ehcache这些缓存依赖包和相关缓存的starter依赖包,然后在启动类中加入@EnableCaching注解,然后在需要的地方就可以使用@Cacheable和@CacheEvict使用和删除缓存了.这个使用很简单,相信用过springboot缓存的都会玩,这里就不再多说了.美中不足的是,springboot使用了插件式的集成方式,虽然用起来很方便,但是当你集成ehcache的时候就是用ehcache,集成redi

springboot中使用redis的方法代码详解

特别说明: 本文针对的是新版 spring boot 2.1.3,其 spring data 依赖为 spring-boot-starter-data-redis,且其默认连接池为 lettuce ​redis 作为一个高性能的内存数据库,如果不会用就太落伍了,之前在 node.js 中用过 redis,本篇记录如何将 redis 集成到 spring boot 中.提供 redis 操作类,和注解使用 redis 两种方式.主要内容如下: •docker 安装 redis •springboo