@PostConstruct用法、InitializingBean、构造方法等
一、介绍:
PostConstruct 是 Java EE(现 Jakarta EE)的注解,Spring 也支持。它用于标记一个方法,在 Bean 初始化完成后、依赖注入完成之后 立即执行。
执行时机:Bean 实例化 → 依赖注入 → @PostConstruct → Bean 准备就绪
二、基本用法:
package com.csii.test.spring;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
@Slf4j
public class TestPostConstructService {
@PostConstruct
public void init() {
log.info("Bean 初始化完成,执行初始化逻辑");
loadConfigurations();
initializeResources();
}
private void loadConfigurations() {
log.info("加载配置...");
}
private void initializeResources() {
log.info("初始化资源...");
}
}
启动项目,查看打印日志:
2026-01-20 14:43:21.266 INFO 23888 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2026-01-20 14:43:21.279 INFO 23888 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2026-01-20 14:43:21.279 INFO 23888 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.30]
2026-01-20 14:43:21.399 INFO 23888 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2026-01-20 14:43:21.399 INFO 23888 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1197 ms
2026-01-20 14:43:21.499 INFO 23888 --- [ main] c.c.t.spring.TestPostConstructService : Bean 初始化完成,执行初始化逻辑
2026-01-20 14:43:21.499 INFO 23888 --- [ main] c.c.t.spring.TestPostConstructService : 加载配置...
2026-01-20 14:43:21.499 INFO 23888 --- [ main] c.c.t.spring.TestPostConstructService : 初始化资源...
2026-01-20 14:43:21.657 INFO 23888 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2026-01-20 14:43:21.896 INFO 23888 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
三、常见使用场景:
场景1:数据初始化
@Component
public class CacheManager {
private Map<String, Object> cache;
@PostConstruct
public void initCache() {
cache = new ConcurrentHashMap<>();
// 从数据库加载初始数据到缓存
loadInitialDataToCache();
System.out.println("缓存初始化完成");
}
}
场景2:配置验证
@Configuration
public class AppConfig {
@Value("${app.max.connections}")
private int maxConnections;
@PostConstruct
public void validateConfig() {
if (maxConnections <= 0) {
throw new IllegalArgumentException("最大连接数必须大于0");
}
System.out.println("配置验证通过");
}
}
场景3:注册监听器或处理器:
@Component
public class EventProcessor {
@Autowired
private ApplicationEventPublisher eventPublisher;
private List<EventListener> listeners = new ArrayList<>();
@PostConstruct
public void registerListeners() {
listeners.add(new EmailEventListener());
listeners.add(new SmsEventListener());
listeners.add(new LogEventListener());
// 注册到事件发布器
listeners.forEach(listener ->
eventPublisher.addApplicationListener(listener)
);
}
}
四、与InitializingBean接口比较
// 使用 @PostConstruct(推荐)
@Component
public class MyService1 {
@PostConstruct
public void init() {
// 初始化代码
}
}
// 使用 InitializingBean 接口
@Component
public class MyService2 implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
// 初始化代码
}
}
// 两者可以共存,执行顺序:@PostConstruct → afterPropertiesSet()
五、实际项目中的最佳实践
实践1:初始化配置类
@Configuration
@PropertySource("classpath:application.properties")
public class DatabaseConfig {
@Value("${spring.datasource.url}")
private String dbUrl;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
private DataSource dataSource;
@PostConstruct
public void initDataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(dbUrl);
config.setUsername(username);
config.setPassword(password);
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
this.dataSource = new HikariDataSource(config);
System.out.println("数据源初始化完成");
}
@Bean
public DataSource dataSource() {
return dataSource;
}
}
六、替代方案:ApplicationRunner或CommandLineRunner:
对于需要在所有Bean初始化完成后执行的启动逻辑:
@Component
@Order(1) // 可以指定执行顺序
public class AppStartupRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
// 在所有 Bean 初始化完成后执行
System.out.println("应用启动完成,执行启动任务");
}
}
// 或者使用 CommandLineRunner
@Component
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
// 处理命令行参数
}
}
七、@PostConstruct、InitializingBean、构造方法三者的执行顺序和区别
这是 Spring Bean 生命周期的核心知识点。
1)、三种"初始化"方式的对比
Spring Bean 有三个阶段可以写"初始化逻辑",按执行顺序:
| 顺序 | 方式 | 写法 | 触发时机 |
| 1 | 构造方法 | public XxxServiceImpl() { ... } | 实例化时,最早 |
| 2 | @PostConstruct | @PostConstruct public void init() { ... } | 依赖注入完成后 |
| 3 | InitializingBean | 实现 afterPropertiesSet() | @PostConstruct 之后 |
还有一个收尾的:
| 4 | @PreDestroy | @PreConstruct public void destroy() { ... } | 容器关闭、Bean 销毁前 |
2)、用例子讲清楚执行顺序
假设有这样一个 Service:
@Service
public class DemoServiceImpl implements InitializingBean {
@Autowired
private SomeDependency dep; // 依赖
private OkHttpClient client;
// 1️⃣ 构造方法
public DemoServiceImpl() {
System.out.println("① 构造方法执行");
// ⚠️ 这里 this.dep 还是 null!
this.client = new OkHttpClient.Builder().build();
}
// 2️⃣ @PostConstruct
@PostConstruct
public void init() {
System.out.println("② @PostConstruct 执行");
// ✅ 这里 this.dep 已经注入好了,可以用
System.out.println("dep = " + this.dep);
}
// 3️⃣ InitializingBean
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("③ afterPropertiesSet 执行");
// ✅ 这里也能用 dep
}
// 4️⃣ @PreDestroy
@PreDestroy
public void cleanup() {
System.out.println("④ @PreDestroy 执行,容器关闭了");
client.dispatcher().executorService().shutdown(); // 释放资源
}
}
启动应用时,控制台输出顺序:
① 构造方法执行
② @PostConstruct 执行
dep = com.xxx.SomeDependency@1234 ← 依赖已就绪
③ afterPropertiesSet 执行
关闭应用时:
④ @PreDestroy 执行,容器关闭了
3)、为什么顺序是这样的?(核心原理)
这是整个知识点的灵魂,理解了就忘不掉:
Spring 创建一个 Bean 的完整步骤
第 1 步:实例化(Instantiation)
↓ Spring 通过反射调用构造方法 new 出对象
↓ 此刻:对象有了,但字段依赖还没注入
↓ 所以构造方法里 @Autowired 的字段还是 null ⚠️
第 2 步:属性注入(Populate Properties)
↓ Spring 处理 @Autowired / @Value,把依赖塞进去
↓ 此刻:dep 字段才有值
第 3 步:Aware 回调(了解即可)
↓ 如果 Bean 实现了 BeanNameAware 等,Spring 回调它
第 4 步:BeanPostProcessor 前置处理
↓ (AOP 代理就是在这一步生成的)
第 5 步:初始化方法
↓ 5a. @PostConstruct ← 注入完成,可以用了
↓ 5b. afterPropertiesSet() ← 紧接着
↓ 5c. 自定义 init-method
第 6 步:BeanPostProcessor 后置处理
↓ 最终的代理对象生成完毕
第 7 步:Bean 就绪,放进容器,可以被 @Autowired 注入到别处
↓
... 应用运行期 ...
↓
第 8 步:容器关闭
↓ @PreDestroy → destroy() 方法
关键认知
构造方法 → 注入 → @PostConstruct 这个顺序决定了:
构造方法里不能用 @Autowired 的依赖(还是 null)
@PostConstruct 里能用依赖(已注入完成)
这就是为什么很多人初始化逻辑放在 @PostConstruct 而不是构造方法
4)、回到 HttpServiceImpl,为什么用构造方法而不是 @PostConstruct?
看当前代码:
@Service
public class HttpServiceImpl implements HttpService {
private final OkHttpClient httpClient; // final!
private final ObjectMapper mapper; // final!
public HttpServiceImpl() {
this.httpClient = new OkHttpClient.Builder()...build();
this.mapper = JsonMapper.builder()...build();
}
}
原因:作者要初始化的两个对象 不依赖任何注入的 Bean(OkHttpClient 和 ObjectMapper 都是直接 new 出来的),所以放在构造方法里就行,还能用 final 保证不可变。
对比:如果初始化逻辑依赖注入的 Bean,就必须用 @PostConstruct
@Service
public class AnotherServiceImpl {
@Autowired
private EnvService envService; // 注入的依赖
private String erpUrl;
// ❌ 错误:构造方法里 envService 还是 null,会 NPE
public AnotherServiceImpl() {
this.erpUrl = envService.getProperty("erp.baseURL"); // NPE!
}
// ✅ 正确:用 @PostConstruct,这时 envService 已注入
@PostConstruct
public void init() {
this.erpUrl = envService.getProperty("erp.baseURL"); // OK
}
}
5)、选型建议(实战总结)
| 场景 | 推荐方式 |
| 初始化不依赖注入对象(如 new OkHttpClient) | 构造方法 ✅(能用 final,最干净) |
| 初始化依赖注入对象(如读配置、调其他 Bean) | @PostConstruct ✅(最常用、最直观) |
| 框架级强约束(必须实现接口的规范) | InitializingBean(业务代码少用,偏框架) |
| 释放资源(关连接池、关线程) | @PreDestroy ✅ |
一个完整的最佳实践模板
@Service
public class MyServiceImpl implements MyService {
private final OkHttpClient client; // final:构造方法初始化
private String baseUrl; // 非 final:依赖注入后初始化
@Autowired
private EnvService envService; // 注入的依赖
public MyServiceImpl() {
this.client = new OkHttpClient.Builder().build(); // 不依赖注入,放构造方法
}
@PostConstruct
public void init() {
this.baseUrl = envService.getProperty("my.url"); // 依赖注入,放 @PostConstruct
}
@PreDestroy
public void destroy() {
client.dispatcher().executorService().shutdown(); // 释放资源
}
}
6)、面试常考的一句话总结
Spring Bean 生命周期:实例化(构造方法)→ 属性注入(@Autowired)→ 初始化(@PostConstruct → afterPropertiesSet)→ 使用 → 销毁(@PreDestroy)。
记住这条主线,再记住**"构造方法里依赖还没注入,@PostConstruct 里才有"**这一条核心规则,就够用了。
八、spring中使用构造方法注入:
在 Spring 中,构造方法注入是官方最推荐的依赖注入方式。默认使用构造方法注入 + final 修饰字段,这是 Spring 官方和社区公认的最佳实践。
1. 标准写法(推荐)
从 Spring 4.3 开始,如果类只有一个构造方法,可以省略 @Autowired 注解。
@Service
public class UserService {
private final UserRepository userRepository;
private final MailService mailService;
// 只有一个构造方法,@Autowired 可以省略
public UserService(UserRepository userRepository, MailService mailService) {
this.userRepository = userRepository;
this.mailService = mailService;
}
public void doSomething() {
userRepository.save();
}
}
2. 显式加 @Autowired(多个构造方法时)
如果类有多个构造方法,必须用 @Autowired 指定使用哪一个。
@Service
public class UserService {
private final UserRepository userRepository;
private final MailService mailService;
@Autowired
public UserService(UserRepository userRepository, MailService mailService) {
this.userRepository = userRepository;
this.mailService = mailService;
}
// 另一个构造方法(不会自动注入)
public UserService(UserRepository userRepository) {
this(userRepository, null);
}
}
3. 使用 Lombok 简化(最简洁)
结合 @RequiredArgsConstructor,可以省去手写构造方法。
@Service
@RequiredArgsConstructor // 会为 final 字段生成构造方法
public class UserService {
private final UserRepository userRepository;
private final MailService mailService;
public void doSomething() {
userRepository.save();
}
}
4. XML 配置方式(旧式)
如果使用 XML 配置,写法如下:
<bean id="userService" class="com.example.UserService">
<constructor-arg ref="userRepository"/>
<constructor-arg ref="mailService"/>
</bean>
对应的 Java 类无需任何注解:
public class UserService {
private final UserRepository userRepository;
private final MailService mailService;
public UserService(UserRepository userRepository, MailService mailService) {
this.userRepository = userRepository;
this.mailService = mailService;
}
}
--
浙公网安备 33010602011771号