目录

SpringPostConstruct详解

【Spring】@PostConstruct详解

在 Java 开发中,尤其是在基于 Spring 框架的项目里,我们常常会遇到需要在对象创建并完成依赖注入后,执行一些初始化操作的场景。 @PostConstruct 注解正是为解决此类问题而诞生的,它为我们提供了一种便捷且优雅的方式来处理对象的初始化逻辑。 @PostConstruct 是JSR-250规范定义的注解,用于标记在对象构造完成且依赖注入完成后执行的初始化方法。在Spring框架中的执行顺序为:构造函数 -> @Autowired依赖注入 -> @PostConstruct方法 -> Bean初始化完成

一、@PostConstruct 基础概念

@PostConstruct 是 Java EE 提供的JSR-250规范注解,的注解,作用是标记一个方法,该方法会在对象被创建并且依赖注入完成之后,在构造函数执行完毕后自动调用,无需手动调用。这使得我们能够在对象可用之前,完成一些必要的初始化工作,比如加载配置文件、建立数据库连接、初始化缓存等。

https://i-blog.csdnimg.cn/direct/2882dcaba52b499f99409a6310cdd89c.png

根据官方代码的注释我们可以看出

  1. @PostConstruct 注解用于需要在完成依赖注入后执行以执行任何初始化的方法。
  2. 所有支持依赖关系注入的类都必须支持此注解。就算注解所在的类不请求注入任何资源,也必须调用带有 @PostConstruct 注释的方法。

使用条件:

  • 只有一个方法可以被 @PostConstruct 标注注解(经测试,在Springboot环境中不生效,可以多个方法标记)

  • @PostConstruct 注解的方法不能有参数,除非是在拦截器类中,在这种情况下,它采用 Interceptors 规范定义的 InvocationContext 对象。

    如果是拦截器类上定义的话,方法必须具有以下签名:

    1. void <METHOD>(InvocationContext)

    2. Object <METHOD>(InvocationContext) throws Exception

      注意:PostConstruct 拦截器方法不得引发应用程序异常,但如果相同的拦截器方法除了生命周期事件之外还插入到业务或超时方法上,则可以声明它抛出检查异常,包括 java.lang.Exception 。如果 PostConstruct 拦截器方法返回一个值,则容器将忽略该值。

  • 在非拦截器的类上 @PostConstruct 注解定义的方法返回值必须是void

  • @PostConstruct 注解的方法访问修饰符可以是 public、protected、package private 或 private,即所有访问级别都可以。

  • @PostConstruct 注解的方法不能用 static 修饰,除了在Application客户端中。

  • @PostConstruct 注解的方法可以 final 修饰

  • @PostConstruct 注解的方法不能抛出未经检查的异常(unchecked exception)

二、核心原理与执行顺序

执行顺序

https://i-blog.csdnimg.cn/direct/316680cbea6f4aacb7ccf05d90391287.png

@PostConstruct用于标记在对象构造完成且依赖注入完成后执行的初始化方法。在Spring框架中的执行顺序为:

构造函数 -> @Autowired依赖注入 -> @PostConstruct方法 -> Bean初始化完成 -> destory方法

源码分析

进入 @PostConstruct 的源码中,发现只有 CommonAnnotationBeanPostProcessor 这个类的下面方法用到

https://i-blog.csdnimg.cn/direct/e6a9a2a956754860be8f59394e3e1035.png

这个方法也很简单,就是把这个注解赋值到 CommonAnnotationBeanPostProcessor 的父类 InitDestroyAnnotationBeanPostProcessor 中的 initAnnotationType 字段

https://i-blog.csdnimg.cn/direct/e62bd87ef50b4591879f186fee43825e.png

那么字段又在哪里使用呢? 很巧,只有 InitDestroyAnnotationBeanPostProcessor 中使用,那我们直接看使用的方法

https://i-blog.csdnimg.cn/direct/9ad763c23154483aa4c90ff66e47a4e4.png

buildLifecycleMetadata 方法主要做了

  1. 先判断这个类或类中方法或字段是否有 initAnnotationTypedestroyAnnotationType 注解
  2. 将该类以及所有父类的所有方法 initAnnotationTypedestroyAnnotationType 注解的方法,作为参数创建 LifecycleElement 对象并存入 currInitMethodscurrDestroyMethods
  3. 把上面的初始化和销毁方法方法作为参数创建 LifecycleMetadata 对象

通过上面步骤,我们就可以发现,在Spring中有多个初始化方法是不会报错的,相反而是全部存入 currInitMethods 集合中,并且所有的父类都会存入这个集合,

https://i-blog.csdnimg.cn/direct/1b3a4f4e6934469fa51f592faab1421e.png

其中在第二步创建 LifecycleElement 对象时,可以神奇的发现这里,如果方法的参数数量不为0就会抛出异常,这就和使用条件中的 @PostConstruct 注解的方法不能有参数相对应了。

那么 buildLifecycleMetadata 方法又在哪里被使用?

https://i-blog.csdnimg.cn/direct/aecccebd2f4848c09203c6454c4b02b1.png

可以看到这个方法主要是从 lifecycleMetadataCache 中获取某个类的 LifecycleMetadata ,如果 lifecycleMetadataCache 为空,那么就调用最开始的方法,否则就会从 lifecycleMetadataCache 尝试获取,如果获取不到则通过大名鼎鼎的 Double-Check 方式,也就是双重检索单例模式,并且使用了 ConcurrentHashMap ,来防止并发问题, ConcurrentHashMap 如何防止并发可看相关文章。

三、使用示例

以下通过一个简单的 Spring Boot 项目示例来展示 @PostConstruct 的用法。

首先,创建一个普通的 Java 类,并在其中定义一个带有 @PostConstruct 注解的方法:

@Component
@Slf4j
public class PostConstructTest {
    @Autowired
    private UserMapper userMapper;

    public PostConstructTest() {
        log.info("Constructor");
    }

    @PostConstruct
    public void demo1() {
        if (userMapper != null) {
            log.info("autowired");
        }
        log.info("PostConstruct1");
    }

    @PostConstruct
    public void demo2() {
        log.info("PostConstruct2");
    }
}

然后,启动 Spring Boot 应用程序,观察控制台输出:

2025-03-12 22:40:48.529   c.a.mpdemo1010.config.PostConstructTest  : Constructor
2025-03-12 22:40:49.378   c.a.mpdemo1010.config.PostConstructTest  : autowired
2025-03-12 22:40:49.378  c.a.mpdemo1010.config.PostConstructTest  : PostConstruct1
2025-03-12 22:40:49.378   c.a.mpdemo1010.config.PostConstructTest  : PostConstruct2

从输出结果可以清晰地看到,构造函数先被调用,随后 @PostConstruct 注解的方法被调用。这表明 @PostConstruct 注解的方法确实是在对象创建和依赖注入完成之后执行的。