【Spring】@PostConstruct详解
在 Java 开发中,尤其是在基于 Spring 框架的项目里,我们常常会遇到需要在对象创建并完成依赖注入后,执行一些初始化操作的场景。@PostConstruct
注解正是为解决此类问题而诞生的,它为我们提供了一种便捷且优雅的方式来处理对象的初始化逻辑。@PostConstruct
是JSR-250规范定义的注解,用于标记在对象构造完成且依赖注入完成后执行的初始化方法。在Spring框架中的执行顺序为:构造函数 -> @Autowired依赖注入 -> @PostConstruct方法 -> Bean初始化完成
一、@PostConstruct 基础概念
@PostConstruct 是 Java EE 提供的JSR-250规范注解,的注解,作用是标记一个方法,该方法会在对象被创建并且依赖注入完成之后,在构造函数执行完毕后自动调用,无需手动调用。这使得我们能够在对象可用之前,完成一些必要的初始化工作,比如加载配置文件、建立数据库连接、初始化缓存等。
根据官方代码的注释我们可以看出
@PostConstruct
注解用于需要在完成依赖注入后执行以执行任何初始化的方法。- 所有支持依赖关系注入的类都必须支持此注解。就算注解所在的类不请求注入任何资源,也必须调用带有
@PostConstruct
注释的方法。
使用条件:
- 只有一个方法可以被
@PostConstruct
标注注解(经测试,在Springboot环境中不生效,可以多个方法标记) @PostConstruct
注解的方法不能有参数,除非是在拦截器类中,在这种情况下,它采用 Interceptors 规范定义的 InvocationContext 对象。
如果是拦截器类上定义的话,方法必须具有以下签名:-
void <METHOD>(InvocationContext)
-
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)
二、核心原理与执行顺序
执行顺序
@PostConstruct用于标记在对象构造完成且依赖注入完成后执行的初始化方法。在Spring框架中的执行顺序为:
构造函数 -> @Autowired依赖注入 -> @PostConstruct方法 -> Bean初始化完成 -> destory方法
源码分析
进入@PostConstruct
的源码中,发现只有CommonAnnotationBeanPostProcessor
这个类的下面方法用到
这个方法也很简单,就是把这个注解赋值到CommonAnnotationBeanPostProcessor
的父类InitDestroyAnnotationBeanPostProcessor
中的initAnnotationType
字段
那么字段又在哪里使用呢? 很巧,只有InitDestroyAnnotationBeanPostProcessor
中使用,那我们直接看使用的方法
buildLifecycleMetadata
方法主要做了
- 先判断这个类或类中方法或字段是否有
initAnnotationType
和destroyAnnotationType
注解 - 将该类以及所有父类的所有方法
initAnnotationType
和destroyAnnotationType
注解的方法,作为参数创建LifecycleElement
对象并存入currInitMethods
和currDestroyMethods
中 - 把上面的初始化和销毁方法方法作为参数创建
LifecycleMetadata
对象
通过上面步骤,我们就可以发现,在Spring中有多个初始化方法是不会报错的,相反而是全部存入currInitMethods
集合中,并且所有的父类都会存入这个集合,
其中在第二步创建LifecycleElement
对象时,可以神奇的发现这里,如果方法的参数数量不为0就会抛出异常,这就和使用条件中的@PostConstruct
注解的方法不能有参数相对应了。
那么buildLifecycleMetadata
方法又在哪里被使用?
可以看到这个方法主要是从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
注解的方法确实是在对象创建和依赖注入完成之后执行的。