@PostConstruct, InitializingBean, @Bean(initMethod) — 초기화 전략 완전 비교
빈이 생성된 직후에 실행해야 하는 초기화 로직이 있다면,
@PostConstruct,InitializingBean,@Bean(initMethod)중 무엇을 써야 할까요?
개념 정의
스프링은 빈이 생성되고 의존성이 주입된 직후 에 초기화 로직을 실행하는 3가지 방법을 제공합니다. 커넥션 풀 워밍, 캐시 로딩, 설정 검증 등에 사용합니다.
3가지 방법 비교
| 방법 | 코드 위치 | 스프링 의존성 | 실행 순서 |
|---|---|---|---|
@PostConstruct | 빈 클래스 메서드에 어노테이션 | 없음 (Jakarta EE 표준) | 1번째 |
InitializingBean | 빈 클래스가 인터페이스 구현 | ** 있음** (스프링 인터페이스) | 2번째 |
@Bean(initMethod) | 설정 클래스에서 지정 | ** 없음** (메서드 이름만 지정) | 3번째 |
1. @PostConstruct — 가장 많이 쓰는 방법
@Component
public class CacheWarmer {
private final RedisTemplate<String, String> redis;
@PostConstruct
public void warmUp() {
// 애플리케이션 시작 시 캐시를 미리 채운다
redis.opsForValue().set("config:version", loadVersion());
}
}
Jakarta EE 표준 어노테이션이라 스프링에 종속되지 않습니다. 대부분의 경우 이걸 쓰면 됩니다.
2. InitializingBean — 스프링 프레임워크 내부에서 주로 사용
@Component
public class DataSourceValidator implements InitializingBean {
private final DataSource dataSource;
@Override
public void afterPropertiesSet() throws Exception {
// 모든 프로퍼티 주입 후 DB 연결 검증
try (Connection conn = dataSource.getConnection()) {
if (!conn.isValid(3)) {
throw new IllegalStateException("DB 연결 실패");
}
}
}
}
스프링 인터페이스에 직접 의존하므로 일반 애플리케이션 코드에서는 권장하지 않습니다. 스프링 프레임워크 내부 코드에서 많이 사용됩니다.
3. @Bean(initMethod) — 외부 라이브러리 빈 초기화
@Configuration
public class AppConfig {
@Bean(initMethod = "init")
public ExternalService externalService() {
return new ExternalService(); // 소스 수정 불가
}
}
소스 코드를 수정할 수 없는 외부 라이브러리의 빈에 초기화 로직을 연결할 때 사용합니다.
실행 순서
3가지를 모두 설정하면 다음 순서로 실행됩니다:
@Component
public class OrderService implements InitializingBean {
@PostConstruct
public void postConstruct() {
System.out.println("1. @PostConstruct");
}
@Override
public void afterPropertiesSet() {
System.out.println("2. InitializingBean");
}
// @Bean(initMethod = "customInit")으로 지정된 경우
public void customInit() {
System.out.println("3. initMethod");
}
}
// 출력: 1 → 2 → 3
선택 기준
| 상황 | 선택 | 이유 |
|---|---|---|
| 일반적인 초기화 로직 | @PostConstruct | 간단, 표준, 스프링 비종속 |
| 외부 라이브러리 빈 | @Bean(initMethod) | 소스 수정 불가 |
| 스프링 프레임워크 확장 | InitializingBean | 프레임워크 내부 관례 |
| 초기화 순서가 중요 | 3가지 조합 | 1→2→3 순서 보장 |
주의할 점
1. @PostConstruct에서 다른 빈에 의존하면 순서 문제
@PostConstruct는 해당 빈의 의존성이 주입된 직후에 실행되지만, ** 다른 빈의 @PostConstruct가 먼저 실행됐는지는 보장되지 않습니다.** 빈 A의 @PostConstruct에서 빈 B의 초기화된 데이터에 접근하면, 빈 B가 아직 초기화되지 않았을 수 있습니다.
@PostConstruct
public void init() {
// configService의 @PostConstruct가 먼저 실행됐는지 보장 안 됨!
String value = configService.getCachedValue();
}
해결: @DependsOn으로 순서를 명시하거나, ApplicationRunner를 사용합니다.
2. 생성자에서 초기화하면 안 되나?
의존성 주입이 생성자 주입이면 생성자 시점에 모든 의존성이 있으므로, 간단한 초기화는 생성자에서 해도 됩니다. 하지만 ** 프록시 기반 기능(@Transactional, @Cacheable 등)은 생성자 시점에 동작하지 않습니다.** 프록시는 빈이 완전히 생성된 후에 감싸지기 때문입니다.
정리
| 방법 | 용도 | 스프링 의존 | 외부 라이브러리 |
|---|---|---|---|
@PostConstruct | 일반 초기화 | 없음 | 불가 |
InitializingBean | 프레임워크 확장 | 있음 | 불가 |
@Bean(initMethod) | 외부 라이브러리 | 없음 | ** 가능** |
대부분의 경우
@PostConstruct를 쓰면 됩니다. 외부 라이브러리 빈만@Bean(initMethod)로 처리합니다.