Post

Spring Lifecycle

Spring Lifecycle

스프링 프레임워크는 스프링 컨테이너라는 틀 안에서 모든 객체가 실행되고 종료된다. 스프링 컨테이너는 Bean이라는 객체를 생성, 의존성 주입, 생명주기 관리, 설정 등을 담당하는 핵심 구성요소이다.

이 스프링 컨테이너가 관리하는 Bean은 공통적인 생명주기(lifecycle)을 갖는다. 이는 스프링 컨테이너가 Bean 객체를 생성, 초기화, 사용, 소멸하는 전체 과정을 의미한다. 따라서 이를 이해하면 Bean의 생명주기를 제어하고 필요한 시점에 원하는 작업을 수행할 수 있다. 전체 흐름은 다음과 같다.

  1. Spring 컨테이너 초기화
  2. Bean 인스턴스 생성
  3. 의존성 주입 (DI)
  4. Bean 이름 설정 및 BeanFactory 설정
  5. 초기화 콜백 (@PostConstruct, InitializingBean 등)
  6. Bean 사용
  7. 소멸 콜백 (@PreDestroy, DisposableBean 등)
  8. 컨테이너 종료

1. Spring 컨테이너 초기화

Spring 빈 생명주기에서 스프링 컨테이너 초기화 단계는 가장 첫 단계로, 전체 애플리케이션 동작의 출발점이다. 이 단계에서는 Spring 컨테이너(ApplicationContext)가 생성되고, 구성정보(@Configuration, @ComponentScan, @Bean)를 바탕으로 Bean들을 준비한다.

  • ApplicationContext 또는 AnnotationConfigApplicationContext 등의 컨테이너 객체 생성
  • 설정 클래스(@Configuration, XML 등) 기반으로 Bean 정의 로딩
  • 빈 정의(BeanDefinition) 파싱 및 저장
  • 빈 클래스 인스턴스 생성
  • 의존성 주입 수행
  • 초기화 콜백 호출

전체 흐름 요약 (Spring Boot 기준)

1
2
3
4
5
6
7
8
9
10
11
SpringApplication.run()  
   ↓
ApplicationContext 생성 (IoC 컨테이너 생성)  
   ↓
@Configuration, @ComponentScan 기반 BeanDefinition 등록  
   ↓
BeanFactoryPostProcessor 적용  
   ↓
Bean 인스턴스 생성 → 의존성 주입 → 초기화  
   ↓
ApplicationRunner / CommandLineRunner 실행  

1.1. ApplicationContext 생성

  • Spring Boot 기준: SpringApplication.run(MyApp.class) 호출
  • 이 때 AnnotationConfigApplicationContext 또는 GenericApplicationContext가 내부적으로 생성됨
  • BeanFactory (IoC 컨테이너의 핵심) 도 함께 구성됨
1
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

1.2. Bean 정의 읽기 (BeanDefinition 생성)

  • @Configuration, @Component, @ComponentScan, @Bean 등을 파싱하여 BeanDefinition 객체 생성
  • BeanDefinition에는 클래스 타입, scope, lazy 여부, 의존성 정보 등 메타정보가 포함됨

예:

1
2
3
4
@Bean
public MyService myService() {
    return new MyServiceImpl();
}

myService라는 이름의 BeanDefinition 생성됨

1.3. BeanFactoryPostProcessor 실행

  • 컨테이너 초기화 과정에서 BeanFactoryPostProcessor (예: ConfigurationClassPostProcessor)가 실행되어,
    • 프로퍼티 값 치환 (@Value)
    • 프로파일 활성화 (@Profile)
    • 설정 클래스에서 추가적인 Bean 등록 등을 처리함

1.4. Bean 생성 전 후처리기 등록 (BeanPostProcessor)

  • @Autowired, @PostConstruct, @PreDestroy 등 처리하기 위한 후처리기(Processor)들이 등록됨
  • AOP 프록시 생성 등도 이 시점에 등록

1.5. Singleton Bean 선 생성 (Eager Init)

  • @Scope("singleton") 빈들은 이 시점에 모두 미리 생성됨
  • 생성자 호출, 의존성 주입(DI), 초기화 메서드까지 실행됨

1.6. ApplicationContext 준비 완료

  • SmartInitializingSingleton, ApplicationListener, @EventListener 등은 컨텍스트가 완전히 초기화된 이후 실행됨
  • ApplicationRunner, CommandLineRunner는 이 시점 이후 실행

내부 구조 (Spring Boot 기준)

1
2
3
4
5
6
7
8
SpringApplication.run()
   └── create ApplicationContext
         └── prepareEnvironment()
         └── create BeanFactory
               └── load BeanDefinition
               └── apply BeanFactoryPostProcessors
               └── register BeanPostProcessors
               └── instantiate singleton Beans

2. Spring Bean 인스턴스 생성

Spring Bean 생명주기에서 Bean 인스턴스 생성 단계컨테이너가 Bean 객체를 메모리에 생성하는 순간으로, 전체 생명주기의 핵심이다. 이 단계에서 객체를 생성하는 방식, 순서, 그리고 확장 포인트를 제대로 이해하면 커스터마이징과 디버깅, 테스트에 유리하다.

Spring Bean 인스턴스 생성은 Spring 컨테이너가 BeanDefinition의 메타정보를 바탕으로 new 키워드를 이용해 실제 객체를 생성하는 과정이다.

Bean 생성 단계 요약

  1. BeanDefinition 등록 (설정 정보 기반)
  2. 인스턴스 생성 (기본 생성자 or 팩토리 메서드)
  3. @Autowired, @Value 기반 의존성 주입
  4. Aware 인터페이스 처리 (BeanNameAware, ApplicationContextAware 등)
  5. 초기화 콜백 처리 (@PostConstruct, afterPropertiesSet(), init-method)
  6. 프록시 처리 (AOP가 적용되면 CGLIB 또는 JDK 프록시로 wrapping)

어떤 방식으로 Bean 인스턴스를 생성하나?

기본 생성자 사용

1
2
3
4
5
6
@Component
public class MyService {
    public MyService() {
        System.out.println("생성자 호출됨");
    }
}
  • 기본 생성자가 자동 호출됨 (리플렉션으로 newInstance() 수행)

생성자 주입 (DI와 결합)

1
2
3
4
5
6
7
8
9
@Component
public class OrderService {
    private final ProductService productService;

    public OrderService(ProductService productService) {
        this.productService = productService;
        System.out.println("OrderService 생성자 호출됨");
    }
}
  • @Autowired 없어도 단일 생성자면 자동 주입
  • Spring이 의존 객체를 먼저 생성한 후, 생성자 호출 시 함께 주입

팩토리 메서드로 생성

1
2
3
4
5
6
7
@Configuration
public class AppConfig {
    @Bean
    public ProductService productService() {
        return new ProductService();  // 개발자가 직접 인스턴스 생성
    }
}
  • @Bean 메서드는 내부적으로 팩토리 메서드 방식으로 Bean을 생성함

내부 동작 흐름 (AbstractAutowireCapableBeanFactory)

1
2
3
4
5
6
7
8
9
doCreateBean()  
   ↓
createBeanInstance()       ← 인스턴스 생성
   ↓
populateBean()             ← 의존성 주입
   ↓
initializeBean()           ← 초기화 콜백
   ↓
applyBeanPostProcessors()  ← 프록시 처리

3. 의존성 주입(DI)

DI는 스프링 프레임워크의 핵심 철학인 IoC(Inversion of Control)를 구체적으로 구현하는 방식이다. 이는 객체가 직접 필요한 의존 객체를 생성하거나 찾지 않고, 외부에서 주입받도록 하는 설계 원칙이다. 전통 방식에서는 A가 B를 new로 직접 생성하지만, DI 방식에서는 A가 B를 외부(Spring Container)로부터 주입받는다.

3.1. 스프링에서 DI 동작 흐름

1
2
3
4
1. 컨테이너가 BeanDefinition 파싱
2. 의존성 관계 파악 (생성자, 필드, 메서드)
3. 의존 대상 Bean 먼저 생성
4. 대상 객체에 주입 (@Autowired, @Inject, @Value 등)

3.2. 주요 DI 애노테이션

애노테이션설명
@Autowired스프링 DI 기본 애노테이션 (생성자, 필드, 세터 등)
@Qualifier("beanName")Bean 이름으로 주입 (동일 타입이 여러 개일 때 사용)
@Value("${config.key}")프로퍼티 값 주입
@InjectJSR-330 표준 (자바 표준 DI), @Autowired 대체 가능
@Resource(name = "beanName")JDK 제공 (byName 주입)

3.3. 내부 동작 구조

1
2
3
4
5
6
7
8
9
10
11
Bean 생성 (AbstractAutowireCapableBeanFactory)
   ↓
populateBean()
   ↓
autowireByType or autowireByName
   ↓
dependencyDescriptor 생성
   ↓
resolveDependency()
   ↓
DI 대상 Bean 주입

3.4. 주의사항

상황고려사항
빈이 순환참조생성자 주입 시 오류 발생 (스프링 5 이후 디폴트로 금지)
다형성 주입@Qualifier, @Primary 사용 필요
선택적 의존성@Autowired(required = false) 또는 Optional<T> 사용
설정 클래스 주입@ConfigurationProperties 또는 @Value 사용

4. Bean 이름 설정 및 BeanFactory 설정

Spring Bean 생명주기에서 “Bean 이름 설정”과 “BeanFactory 설정” 단계는 Spring 컨테이너가 Bean을 생성한 뒤 컨테이너 내부 메타정보를 주입하는 과정이다. 이 과정은 일반적으로 Aware 인터페이스를 통해 제공된다.

이 단계에서 다음과 같은 역할을 한다.

“너는 이름이 ‘orderService’이고, 현재 컨테이너는 이 BeanFactory/ApplicationContext야” → Bean이 자신을 감싸고 있는 Spring 컨테이너 환경 정보에 접근할 수 있게 해준다.

Bean이 생성된 후, 의존성 주입을 마친 후, 초기화 콜백(@PostConstruct) 직전에 발생한다.

4.1. 주요 인터페이스와 역할

인터페이스역할설명
BeanNameAwareBean 이름 설정setBeanName(String name) 호출
BeanClassLoaderAware클래스 로더 정보 설정주로 AOP 프록시 등에 사용됨
BeanFactoryAwareBeanFactory 주입의존 객체 수동 조회 가능
ApplicationContextAwareApplicationContext 주입이벤트 발행, 리소스 로딩 등 가능

4.2. 동작 순서 요약

1
2
3
4
5
6
1. Bean 인스턴스 생성
2. 의존성 주입 (@Autowired)
3. setBeanName() 호출
4. setBeanFactory() 호출
5. setApplicationContext() 호출
6. 초기화 콜백 (@PostConstruct, InitializingBean 등)

4.3. 예제 코드

예제 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component
public class MyBean implements BeanNameAware, BeanFactoryAware, ApplicationContextAware {

    @Override
    public void setBeanName(String name) {
        System.out.println("Bean 이름 설정됨: " + name);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        System.out.println("BeanFactory 설정됨: " + beanFactory.getClass().getSimpleName());
    }

    @Override
    public void setApplicationContext(ApplicationContext context) {
        System.out.println("ApplicationContext 설정됨: " + context.getClass().getSimpleName());
    }
}

실행 결과

1
2
3
Bean 이름 설정됨: myBean
BeanFactory 설정됨: DefaultListableBeanFactory
ApplicationContext 설정됨: AnnotationConfigApplicationContext

4.4. 실무 활용 예시

인터페이스실무 활용 예
BeanFactoryAware다른 Bean을 동적으로 로딩 (beanFactory.getBean(Class<T>))
ApplicationContextAwarecontext.publishEvent() 또는 context.getResource()
BeanNameAwareBean을 Map으로 관리할 때, 이름 기준 식별 필요할 경우

5. 초기화 콜백

Spring에서 초기화 콜백(Initialization Callback)은 Bean이 생성되고 의존성 주입이 완료된 직후에 초기화 로직을 실행하는 방법이다. 즉, “모든 준비가 끝났을 때, 마지막으로 개발자가 하고 싶은 작업을 실행”하는 타이밍이다.

5.1. 동작 순서

1
2
3
4
5
6
1. Bean 생성자 호출
2. @Autowired 등 의존성 주입
3. setBeanName(), setApplicationContext() 등 Aware 인터페이스
4. → @PostConstruct 호출
5. → InitializingBean.afterPropertiesSet() 호출
6. → init-method 호출

5.2. 초기화 콜백 방식 3가지

방법선언 방식설명우선순위
@PostConstruct애노테이션가장 간단하고 실무에서 가장 많이 사용됨1
InitializingBean인터페이스afterPropertiesSet() 메서드 오버라이드2
init-methodXML or @Bean(initMethod="...")선언적 방법 (과거 사용)3

세 가지 방식중 하나로 구현이 가능하다. 만약 셋 다 구현해두었다면 위 순서대로 실행된다.

@PostConstruct – 가장 직관적이고 실무 권장 방식

1
2
3
4
5
6
7
@Component
public class MyService {
    @PostConstruct
    public void init() {
        System.out.println("▶️ @PostConstruct 호출됨");
    }
}
  • 의존성 주입이 끝난 직후 한 번만 호출됨
  • 단, private이어도 호출 가능 (권장: public or protected)
  • javax.annotation 또는 jakarta.annotation 패키지

InitializingBean 인터페이스

1
2
3
4
5
6
7
@Component
public class MyService implements InitializingBean {
    @Override
    public void afterPropertiesSet() {
        System.out.println("▶️ afterPropertiesSet() 호출됨");
    }
}
  • 직접 구현하면 스프링이 자동 호출
  • 구현 시 @PostConstruct와 함께 쓰는 것보다 둘 중 하나만 사용하는 것이 좋음

init-method 방식

Java Config

1
2
3
4
@Bean(initMethod = "init")
public MyService myService() {
    return new MyService();
}

XML 방식

1
<bean id="myService" class="com.example.MyService" init-method="init"/>
  • 메서드 이름으로 지정
  • 해당 메서드는 public이어야 하며 void 반환해야 함
  • 애노테이션보다 덜 직관적 → 스프링 부트에서는 거의 사용하지 않음

5.3. 실무 팁

설명
@PostConstruct 사용 권장가장 직관적이며 테스트에도 적합
테스트 시 주의@PostConstruct는 객체 생성 직후 실행되므로 단위 테스트에서는 mocking 어려울 수 있음
여러 개 중복 정의 x한 Bean에 여러 초기화 방식 동시에 쓰지 말 것 (의도치 않은 호출 순서 발생 가능)
AOP와 충돌@PostConstruct는 AOP Proxy 적용 전 호출됨 → @Transactional은 이 시점에는 미적용

5.4. 요약

항목설명
목적Bean 생성 + 주입 후 초기화 작업 수행
권장 방식@PostConstruct
실행 순서@PostConstructafterPropertiesSet()init-method
주의점AOP 적용 전에 호출됨, 너무 많은 방식 중복 금지

6. Bean 사용

Spring Bean의 생명주기에서 “Bean 사용” 단계는 Bean이 생성되고 초기화된 이후, 실제 애플리케이션 로직 내에서 해당 Bean이 의도된 기능을 수행하는 시점을 의미한다. 이 단계는 개발자 입장에서는 가장 자연스럽게 경험하는 부분이며, Spring 프레임워크는 이 시점을 위해 모든 생명주기 관리 작업을 수행한다. 참고로, AOP Advice도 이 시점에 실행된다. @PostContrcut 시점에는 AOP가 적용되지 않으므로 주의하자.

예를 들어, 다음과 같은 내용들이 이 시점에 실행된다.

  • @Service에서 @Repository 호출
  • @Controller에서 @Service 호출
  • Scheduler, EventListener 내부 로직 실행 등

6.1. 사용 방식 예시

의존성 주입을 통해 사용하는 경우

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class OrderService {
    private final ProductService productService;

    @Autowired
    public OrderService(ProductService productService) {
        this.productService = productService;
    }

    public void order() {
        productService.reduceStock();
    }
}
  • OrderService는 Spring 컨테이너가 생성한 Bean이고,
  • 내부에서 ProductService라는 또 다른 Bean을 사용하고 있음

ApplicationContext에서 직접 꺼내 사용하는 경우

1
2
3
4
5
6
7
8
9
10
11
@Component
public class ManualCaller implements ApplicationRunner {
    @Autowired
    private ApplicationContext context; // ApplicationContext 주입

    @Override
    public void run(ApplicationArguments args) {
        MyService myService = context.getBean(MyService.class);
        myService.execute();  // Bean 사용
    }
}
  • 주로 테스트, 유틸성 코드, 동적 DI 필요 시 사용

Scheduler, EventListener 등을 통한 간접 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class MyScheduler {
    @Scheduled(fixedDelay = 5000)
    public void runScheduledTask() {
        System.out.println("Bean이 스케줄러에 의해 사용됨");
    }
}

@Component
public class EventConsumer {
    @EventListener
    public void handleEvent(MyEvent event) {
        System.out.println("이벤트 발생 시 Bean 사용됨");
    }
}

6.2. Bean 사용 시 특징

항목설명
싱글톤(Singleton)기본 스코프에서는 여러 번 사용해도 하나의 인스턴스를 공유
트랜잭션@Transactional이 붙은 메서드는 Spring AOP로 감싸진 후 사용됨
AOP 적용@Cacheable, @Async, @Retryable 등도 Bean 사용 시 자동 적용
Thread Safety공유 자원이므로 상태를 가질 경우 스레드 안전에 주의 필요
DI 컨텍스트 내부에서만 사용 가능Bean은 Spring 컨테이너가 관리해야 DI, AOP 등이 적용됨 (직접 new 금지)

Bean은 DI 컨텍스트 내부에서만 사용 가능하므로 직접 new로 생성하는 것은 금지한다. new로 생성한 Bean 객체는 컨테이너가 관리하지 않기 때문에 AOP, DI, 라이프사이클에서 모두 정상 동작하지 않는다.

6.3. 실무 활용: Bean을 사용하는 대표적인 상황

상황Bean 사용 방식
웹 요청 처리@Controller@Service@Repository
이벤트 기반 처리@EventListener, ApplicationEventPublisher
비동기 작업@Async 붙은 메서드 호출
배치/Scheduler@Scheduled 사용
도메인 서비스@Component 또는 @Service 기반 Bean 호출

이외에도 싱글톤으로 하나의 객체만 생성해서 사용할만한 클래스라면 Bean으로 등록해놓고 사용할 수 있다. 다만, 공유 자원이므로 상태를 가질 경우 thread safe를 주의해야한다.


7. 소멸 콜백(Destroy Callback)

Spring Bean 생명주기에서 소멸 콜백(Destroy Callback)은 Spring 컨테이너가 종료될 때 Bean이 자원을 반납하거나 정리(clean-up) 작업을 수행할 수 있도록 제공하는 마지막 기회이다. 즉, 컨테이너가 “Bean은 이제 안 써. 정리해.”라고 알리는 시점입니다.

7.1. 소멸 콜백(Destroy Callback)이란?

Spring Bean이 컨테이너에서 제거되기 전에 호출되는 정리(clean-up) 메서드이다. (파일 닫기, DB 연결 해제, 스레드 종료, 로그 기록 등)

7.2. 소멸 콜백 방식 3가지

방식선언 방법설명우선순위
@PreDestroy애노테이션가장 간편하고 실무에서 권장됨1️⃣
DisposableBean인터페이스destroy() 메서드 구현2️⃣
destroyMethodXML 또는 @Bean(destroyMethod="...")선언적 방식3️⃣

@PreDestroy

1
2
3
4
5
6
7
8
@Component
public class MyBean {

    @PreDestroy
    public void preDestroy() {
        System.out.println("🧹 @PreDestroy 호출됨");
    }
}
  • javax.annotation.PreDestroy 또는 jakarta.annotation.PreDestroy
  • 컨테이너가 종료되기 직전에 호출됨
  • Bean이 Spring에 의해 관리될 때만 동작

DisposableBean 인터페이스

1
2
3
4
5
6
7
@Component
public class MyBean implements DisposableBean {
    @Override
    public void destroy() {
        System.out.println("🧹 DisposableBean.destroy() 호출됨");
    }
}
  • destroy() 메서드 오버라이드
  • @PreDestroy와 함께 사용 시 둘 다 호출됨 (@PreDestroydestroy() 순)

destroyMethod 속성 (구식 또는 Java Config)

1
2
3
4
5
6
7
8
9
@Bean(destroyMethod = "cleanup")
public MyBean myBean() {
    return new MyBean();
}
public class MyBean {
    public void cleanup() {
        System.out.println("🧹 destroy-method 호출됨");
    }
}
  • @PreDestroy, destroy()와 달리 빈 등록 시 명시적으로 메서드 이름 지정

7.2. 소멸 콜백 주의사항

주의사항설명
컨테이너 관리 밖의 객체는 콜백 실행 안 됨직접 new 한 객체는 적용되지 않음
프로토타입 스코프 Bean은 소멸 콜백이 호출되지 않음수동으로 destroy() 호출해야 함
AOP 적용 전후와 무관소멸 콜백은 AOP와 충돌 없음
@PreDestroy는 예외 던지지 않는 것이 좋음예외 시 다른 콜백 메서드가 실행되지 않을 수 있음

7.3. 스프링 부트에서 컨텍스트 종료 흐름

1
2
3
4
5
6
Application 종료 시
  → ApplicationContext.close()
    → 모든 Singleton Bean 대상
        → @PreDestroy 호출
        → destroy() 호출
        → destroy-method 호출

7.4. 실제 사용 예시

사용 예설명
커넥션 풀 정리HikariDataSource 등에서 연결 해제
파일/리소스 닫기FileOutputStream.close()
스레드 종료ExecutorService shutdown 등
캐시 삭제Redis 또는 local cache 메모리 해제

7.5. 요약

항목설명
정의Spring 컨테이너가 종료되기 직전 실행되는 정리용 메서드
실행 시점ApplicationContext가 close() 될 때
방법@PreDestroyDisposableBean.destroy()destroy-method
권장 방식@PreDestroy
주의사항프로토타입 Bean에는 자동 호출 x

8. 컨테이너 종료

Spring의 컨테이너 종료(Container Shutdown)는 Spring 애플리케이션이 종료될 때 발생하는 마지막 생명주기 단계로, 컨테이너가 내부 자원과 Bean을 정리하고 안전하게 종료하는 과정이다. 이 과정은 리소스 누수 방지, 외부 연결 종료, 작업 중단 등을 위한 핵심 시점이다. 이때 Spring ApplicationContext더 이상 유효하지 않게 되며, 내부적으로 관리되던 Bean, 쓰레드, 커넥션 등의 자원을 안전하게 정리하는 과정이다.

1
2
3
4
INFO - Shutting down ExecutorService 'applicationTaskExecutor'
INFO - Closing JPA EntityManagerFactory
INFO - Closing ApplicationContext
INFO - ApplicationContext closed and resources released

위와 같은 로그가 바로 이 시점에 출력되는 로그이다.

8.1. 컨테이너 종료 트리거 방식

트리거설명
Ctrl+C 또는 SIGTERMJVM 종료 시 자동 감지
context.close()ConfigurableApplicationContext 수동 종료
SpringApplication.exit(context)Spring Boot에서 명시적 종료
내장 WAS 종료Tomcat/Jetty/Undertow 종료 시 함께 종료됨

8.2. 종료 순서 흐름

1
2
3
4
5
6
7
8
1. JVM 종료 요청 발생
2. Spring의 Shutdown Hook 동작 시작
3. ApplicationContext.close() 호출
4. SmartLifecycle Bean → stop() 호출
5. Singleton Bean → @PreDestroy, destroy() 호출
6. BeanPostProcessor 등 리소스 정리
7. 커넥션 풀, 쓰레드, 캐시 해제
8. JVM 종료

8.3. 종료 시 호출되는 주요 구성요소

순서구성요소설명
1️⃣SmartLifecycle.stop()비동기 작업 정지 (예: 스케줄러, KafkaListener 등)
2️⃣@PreDestroyBean 정리 로직 실행
3️⃣DisposableBean.destroy()명시적 인터페이스 구현
4️⃣@Bean(destroyMethod=...)JavaConfig 또는 XML 기반 정리 메서드 호출
5️⃣ContextClosedEvent애플리케이션 종료 알림 이벤트

8.4. 예제 코드

소멸 콜백 등록된 Bean

1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class MyResource implements DisposableBean {
    @PreDestroy
    public void onPreDestroy() {
        System.out.println("🧹 @PreDestroy 실행됨");
    }

    @Override
    public void destroy() {
        System.out.println("🧹 DisposableBean.destroy() 실행됨");
    }
}

종료 이벤트 리스너

1
2
3
4
5
6
7
@Component
public class ShutdownListener implements ApplicationListener<ContextClosedEvent> {
    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        System.out.println("📦 ApplicationContext 종료 감지됨");
    }
}

8.5. 실무에서 중요한 이유

이유설명
자원 누수 방지DB 연결, Redis, 파일, 커넥션 풀을 반드시 정리해야 함
쓰레드 종료@Async, @Scheduled 스레드 풀 종료 필요
로그 수집애플리케이션 종료 로그 출력 및 종료 시 상태 보고
상태 동기화상태 저장, shutdown 알림 전송 등

8.6. 주의사항

항목설명
프로토타입(Prototype) Bean은 소멸 콜백 호출 x직접 관리 필요
@PreDestroy는 예외 발생 시 중단 가능안정적으로 처리해야 함
쓰레드 풀 종료하지 않으면 JVM 종료 안 됨ExecutorService.shutdown() 필수
강제 종료(System.exit(0)) 시 콜백 실행 안 될 수도 있음Runtime.addShutdownHook() 사용 고려

8.7. 요약 정리

항목설명
정의Spring 컨테이너가 Bean과 리소스를 안전하게 정리하고 종료하는 과정
트리거JVM 종료, context.close(), SpringApplication.exit()
순서SmartLifecycle.stop()@PreDestroydestroy()ContextClosedEvent
주의비동기 작업, 커넥션, 쓰레드 종료 누락 주의
실무 포인트상태 저장, 알림 전송, 외부 연결 해제, shutdown log 처리
This post is licensed under CC BY 4.0 by the author.