본문으로 건너뛰기
김신건의 로그

[Spring] Bean Lifecycle: 초기화와 소멸 콜백

· 수정 · 📖 약 2분 · 718자/단어 #spring #bean #lifecycle #container
spring bean lifecycle, @PostConstruct, @PreDestroy, InitializingBean, DisposableBean, afterPropertiesSet, Bean 생명주기, 스프링 빈 라이프사이클

정의

spring-ioc-diBean 등록과 주입 을 다룬다면, 이 페이지는 등록 이후 생성 → 사용 → 소멸 사이에 끼어드는 콜백을 다룬다. Spring 컨테이너가 단순히 객체를 만들고 끝내는 게 아니라, 의존성 주입 → 초기화 → BeanPostProcessor → 사용 → 소멸까지 여러 단계로 처리한다는 점이 핵심.

시각화

ApplicationContext refresh 시점에 1~6 단계를 모든 singleton Bean 에 대해 순차 실행. close 시점에 8 단계 (등록 역순).

전체 생명주기 단계

1. 컨테이너 시작

2. Bean 메타데이터 등록 (BeanDefinition)

3. BeanFactoryPostProcessor 실행 (BeanDefinition 수정)

4. Bean 인스턴스화 (생성자 호출)

5. 의존성 주입 (필드, setter)

6. Aware 콜백
     ├─ BeanNameAware.setBeanName
     ├─ BeanFactoryAware.setBeanFactory
     └─ ApplicationContextAware.setApplicationContext

7. BeanPostProcessor.postProcessBeforeInitialization

8. 초기화 콜백 (아래 3가지 모두 실행)
     ├─ @PostConstruct
     ├─ InitializingBean.afterPropertiesSet()
     └─ @Bean(initMethod = "...") 또는 init-method

9. BeanPostProcessor.postProcessAfterInitialization
     └─ ← 여기서 AOP proxy 가 생성됨

10. Bean 사용 (애플리케이션 코드)

11. 컨테이너 종료

12. 소멸 콜백 (등록 역순)
      ├─ @PreDestroy
      ├─ DisposableBean.destroy()
      └─ @Bean(destroyMethod = "...") 또는 destroy-method

prototype scope 는 11~12 가 호출되지 않음 (컨테이너가 추적 안 함, spring-bean-scope 참고).

초기화 콜백 3가지

같은 시점에 호출되는 3가지 방법:

@PostConstruct (권장)

@Component
public class CacheLoader {
    @Autowired DataSource dataSource;
    Map<Long, User> cache;

    @PostConstruct
    public void warmup() {
        cache = loadAll(dataSource);
    }
}

JSR-250 표준 (Jakarta Annotations). framework 종속이 없어 가장 깔끔.

InitializingBean 인터페이스

@Component
public class CacheLoader implements InitializingBean {
    @Override
    public void afterPropertiesSet() {
        cache = loadAll(dataSource);
    }
}

Spring 인터페이스에 의존. 추천 안 함.

@Bean(initMethod) 또는 XML init-method

@Bean(initMethod = "warmup")
public CacheLoader cacheLoader() {
    return new CacheLoader();
}

라이브러리 코드처럼 수정 불가능한 클래스 에 초기화 콜백을 붙일 때 유용.

소멸 콜백 3가지

@PreDestroy, DisposableBean.destroy(), @Bean(destroyMethod). 모두 컨테이너 종료 시 호출. AutoCloseable 을 구현한 Bean 은 close() 가 자동으로 destroyMethod 로 추론됨.

@Component
public class ConnectionPool implements AutoCloseable {
    @Override
    public void close() {     // destroyMethod 자동 인식
        // 풀 정리
    }
}

비활성화하려면 @Bean(destroyMethod = "") 명시.

Aware 인터페이스

컨테이너 자체에 접근이 필요할 때:

@Component
public class BeanNameLogger implements BeanNameAware, ApplicationContextAware {
    String myName;
    ApplicationContext ctx;

    @Override
    public void setBeanName(String name) { this.myName = name; }

    @Override
    public void setApplicationContext(ApplicationContext ctx) { this.ctx = ctx; }
}

ApplicationContextAware 보다는 생성자 주입으로 ApplicationContext 받기 가 권장. Aware 는 framework-coupled 코드.

종료 순서

소멸은 등록의 역순. 의존성이 있는 Bean (A → B) 에서 A 가 먼저 소멸되어야 B 를 안전하게 사용할 수 있으므로:

init order:  B → A   (A 가 B 에 의존)
destroy order: A → B

@DependsOn 으로 명시할 수도 있지만, 일반적으로는 컨테이너가 의존성 그래프를 분석해 자동 처리.

SmartLifecycle

서버 시작/종료에 시점 제어가 필요한 컴포넌트 (Kafka consumer, scheduler 등):

@Component
public class KafkaConsumerWrapper implements SmartLifecycle {
    boolean running;

    @Override public void start() { /* consumer 시작 */ running = true; }
    @Override public void stop() { running = false; /* 정리 */ }
    @Override public boolean isRunning() { return running; }
    @Override public int getPhase() { return 100; }   // phase 낮을수록 먼저 시작 / 늦게 종료
    @Override public boolean isAutoStartup() { return true; }
}

세 콜백을 한 클래스에 다 두면 호출 순서를 직접 확인할 수 있다:

java
@Component
public class CacheLoader implements InitializingBean, DisposableBean {

  @PostConstruct
  public void postConstruct() { System.out.println("1. @PostConstruct"); }

  @Override
  public void afterPropertiesSet() { System.out.println("2. InitializingBean"); }

  public void initMethod() { System.out.println("3. @Bean(initMethod)"); }

  @PreDestroy
  public void preDestroy() { System.out.println("a. @PreDestroy"); }

  @Override
  public void destroy() { System.out.println("b. DisposableBean"); }

  public void destroyMethod() { System.out.println("c. @Bean(destroyMethod)"); }
}

@Configuration
class Config {
  @Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")
  CacheLoader cacheLoader() { return new CacheLoader(); }
}
application log
1. @PostConstruct
2. InitializingBean
3. @Bean(initMethod)
... 컨테이너 종료 ...
a. @PreDestroy
b. DisposableBean
c. @Bean(destroyMethod)

SmartLifecycle 와 다른 점:

  • 컨테이너의 모든 Bean 이 초기화된 start() 호출 (refresh 완료 시점)
  • phase 로 시작/종료 순서 정밀 제어
  • stop() 은 graceful shutdown 호출

함정과 베스트 프랙티스

  • @PostConstruct 권장: 표준, 가독성, framework 독립
  • 초기화 콜백에서 무거운 작업 주의: 컨테이너 시작 지연 + 실패 시 컨테이너 종료
  • 소멸 콜백은 빠르게: graceful shutdown timeout 안에 끝나야
  • @Lazy 의 함정: 첫 사용 시점에 초기화. 초기화 실패가 런타임에 터짐.
  • 순환 의존성: 생성자 주입에선 컨테이너 시작 시 즉시 에러. setter 주입에선 가능하지만 권장 안 됨.
  • prototype scope 의 destroy 콜백은 호출 안 됨: 직접 cleanup 필요
  • @PreDestroy 가 호출되려면 정상 종료: kill -9 / OOM 등은 불가

관련 위키

이 글의 용어 (6개)
[Spring] @Autowired 해결 전략: @Qualifier, @Primary, byTypespring
정의 가 의 기초 (생성자/필드/setter 주입) 를 다룬다면, 이 페이지는 같은 타입의 Bean 이 여러 개일 때 어떻게 하나를 골라내는지 의 해결 전략을 다룬다. 기본 알고…
[Spring] @Conditional, Profile, @ConfigurationPropertiesspring
정의 Spring Boot는 조건부 Bean 등록 메커니즘을 제공한다. classpath, property, 다른 Bean 유무 등에 따라 자동 구성을 활성화/비활성화. , 어노…
[Spring] Bean Scope 심화: Singleton, Prototype, Web Scopespring
정의 가 5가지 scope 의 개요 를 다룬다면, 이 페이지는 그 중 함정 (prototype-into-singleton) 과 해결 패턴 (ScopedProxy, ObjectPr…
[Spring] BeanPostProcessor와 BeanFactoryPostProcessorspring
정의 Spring 컨테이너의 2가지 확장점: - BeanFactoryPostProcessor (BFPP): Bean 인스턴스가 만들어지기 전, (메타데이터) 을 수정. 클래스명,…
[Spring] IoC와 DI: Bean, ApplicationContext, @Autowiredspring
정의 IoC (Inversion of Control)는 객체 생성/생명주기 제어를 프레임워크가 가져가는 원칙. DI (Dependency Injection)는 그 구현 방법. S…
[Spring] Stereotype 어노테이션: @Component, @Service, @Repository, @Controllerspring
정의 Stereotype 어노테이션 은 클래스가 Spring 컨테이너의 어떤 역할 (role) 인지를 의미적으로 표시한다. 모두 를 메타 어노테이션 으로 가진 specializa…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기