@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 在单个 Bean 初始化完成后立即执行

  • 适合执行该 Bean 自身的初始化逻辑

  • 不要在其中执行耗时操作

  • 对于需要在所有 Bean 都准备好后执行的逻辑,使用 ApplicationRunner 或 CommandLineRunner

 

七、@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;
    }
}

 

 

 

 

--

posted on 2026-01-20 15:00  有点懒惰的大青年  阅读(43)  评论(0)    收藏  举报