4 분 소요

개요

스프링의 IoC(Inversion of Control) 컨테이너는 Java의 가비지 컬렉션처럼 객체의 생명주기를 자동으로 관리합니다. 개발자는 더 이상 언제 객체를 생성하고 소멸시킬지 걱정할 필요가 없습니다.

왜 생명주기 관리가 중요한가?

🏠 식당 운영에 비유하면:

  • 준비 단계: 식당 건물 짓기 (인스턴스화)
  • 설비 설치: 주방기구, 테이블 배치 (의존성 주입)
  • 오픈 준비: 직원 교육, 메뉴 준비 (초기화)
  • 영업 종료: 정리, 청소, 문 잠그기 (소멸)

각 단계를 올바른 순서로 진행해야 식당이 제대로 운영되는 것처럼, 빈도 올바른 생명주기를 거쳐야 합니다.

스프링 빈 생명주기 4단계

flowchart TD
    A[🏗️ 1. 인스턴스화<br/>new Bean<br/>생성자 호출] --> B[🔌 2. 의존성 주입<br/>@Autowired<br/>필드/세터 주입]
    
    B --> C[⚡ 3. 초기화<br/>@PostConstruct<br/>InitializingBean<br/>init-method]
    
    C --> D[✅ 빈 사용 준비<br/>비즈니스 로직 실행]
    
    D --> E[🗑️ 4. 소멸<br/>@PreDestroy<br/>DisposableBean<br/>destroy-method]
    
    %% 스타일링
    classDef create fill:#fff3e0,stroke:#e65100,stroke-width:3px,color:#000
    classDef inject fill:#f3e5f5,stroke:#4a148c,stroke-width:3px,color:#000
    classDef init fill:#e8f5e8,stroke:#1b5e20,stroke-width:3px,color:#000
    classDef ready fill:#e0f2f1,stroke:#00695c,stroke-width:3px,color:#000
    classDef destroy fill:#ffebee,stroke:#b71c1c,stroke-width:3px,color:#000
    
    class A create
    class B inject
    class C init
    class D ready
    class E destroy

1단계: 인스턴스화 (Instantiation) 🏗️

1
2
// 스프링이 내부적으로 수행
MyService service = new MyService(); // 생성자 호출
  • 언제: 가장 첫 번째 단계
  • 무엇을: 메모리에 객체 인스턴스 생성
  • 특징: 아직 의존성이 주입되지 않은 상태

2단계: 의존성 주입 (Dependency Injection) 🔌

1
2
3
// 스프링이 내부적으로 수행
service.setUserRepository(userRepository);  // 세터 주입
// 또는 @Autowired 필드에 값 할당
  • 언제: 인스턴스화 직후
  • 무엇을: 필요한 의존성들을 주입
  • 특징: 이제 협력 객체들과 연결된 상태

3단계: 초기화 (Initialization) ⚡

1
2
3
4
5
// 개발자가 정의한 초기화 로직 실행
@PostConstruct
public void init() {
    // 초기화 로직
}
  • 언제: 의존성 주입 완료 후
  • 무엇을: 개발자가 정의한 초기화 로직 실행
  • 특징: 모든 의존성을 사용할 수 있는 상태

4단계: 소멸 (Destruction) 🗑️

1
2
3
4
5
// 개발자가 정의한 정리 로직 실행
@PreDestroy
public void cleanup() {
    // 정리 로직
}
  • 언제: 컨테이너 종료 시 또는 빈 스코프 종료 시
  • 무엇을: 개발자가 정의한 정리 로직 실행
  • 특징: 자원 해제, 연결 닫기 등

콜백 (Callback)

💡 콜백(Callback)이란?

콜백은 “약속된 시점에 자동으로 울리는 알람”입니다.
스프링 컨테이너가 빈의 생명주기를 관리하면서, 특정 순간(초기화 완료 시점, 소멸 직전 등)에 개발자가 미리 정의해둔 메서드를 자동으로 호출해주는 메커니즘입니다.

예를 들어 @PostConstruct는 “의존성 주입이 끝나면 이 메서드를 호출해줘!”라고 스프링에게 미리 약속하는 것이죠.
마치 “아침 7시가 되면 알람 울려줘!”와 같은 원리입니다. ⏰

초기화 콜백 순서도

---
title: 초기화 콜백 실행 순서
---
sequenceDiagram
    participant C as Spring Container
    participant B as Bean Instance
    participant P as BeanPostProcessor
    participant D as Dependencies
    
    Note over C,D: 인스턴스화 단계
    C->>B: new Bean() - 생성자 호출
    Note right of B: 의존성 아직 없음
    
    Note over C,D: 의존성 주입 단계
    C->>D: 의존성 해결
    C->>B: @Autowired 필드/세터 주입
    Note right of B: 의존성 사용 가능
    
    Note over C,D: 초기화 단계
    C->>P: postProcessBeforeInitialization(bean)
    P-->>C: bean (수정된 빈 반환 가능)
    
    C->>B: @PostConstruct 메서드 호출
    Note right of B: JSR-250 어노테이션
    
    C->>B: afterPropertiesSet() 호출
    Note right of B: InitializingBean 인터페이스
    
    C->>B: 커스텀 init-method 호출
    Note right of B: XML/Java Config 설정
    
    C->>P: postProcessAfterInitialization(bean)
    P-->>C: bean (프록시 반환 가능)
    
    Note right of B: ✅ 빈 사용 준비 완료

1. JSR-250 어노테이션 (✅ 권장)

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class RestaurantService {
    
    @PostConstruct  // 초기화
    public void openRestaurant() {
        System.out.println("🍽️ 식당 오픈 준비 완료!");
    }
    
    @PreDestroy     // 소멸
    public void closeRestaurant() {
        System.out.println("🔒 식당 문 닫고 정리 완료!");
    }
}

장점:

  • 표준 API (이식성 좋음)
  • 스프링에 종속되지 않음
  • 코드가 깔끔함

2. 스프링 인터페이스

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class RestaurantService implements InitializingBean, DisposableBean {
    
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("🍽️ 초기화: 모든 설정 완료!");
    }
    
    @Override
    public void destroy() throws Exception {
        System.out.println("🔒 소멸: 정리 작업 완료!");
    }
}

특징:

  • 스프링에 종속됨
  • 명시적인 인터페이스 계약
  • 컴파일 시점에 검증 가능

실행 순서

초기화 순서 📊

순서 단계 코드 예시 의존성 사용 주의사항
1 생성자 new Bean() 의존성 사용 금지
2 의존성 주입 @Autowired 스프링이 자동 처리
3 @PostConstruct @PostConstruct 권장 방식
4 InitializingBean afterPropertiesSet() 스프링 종속
5 init-method @Bean(initMethod) 외부 라이브러리용

소멸 순서 📊

순서 단계 코드 예시 사용 시기 특징
1 @PreDestroy @PreDestroy 권장 방식 표준 API
2 DisposableBean destroy() 레거시/특수 스프링 종속
3 destroy-method @Bean(destroyMethod) 외부 라이브러리 설정 기반

실습 예제

예제 1: 기본 생명주기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
public class CoffeeShop {
    // 1. 생성자
    public CoffeeShop() {
        System.out.println("☕ 1단계: 카페 건물 완성 (생성자)");
    }
    // 2. 의존성 주입 후 초기화
    @PostConstruct
    public void openCafe() {
        System.out.println("☕ 3단계: 카페 오픈 준비 완료!");
    }
    // 4. 소멸 전 정리
    @PreDestroy
    public void closeCafe() {
        System.out.println("☕ 4단계: 카페 문 닫기");
    }
}

예제 2: 모든 콜백 메커니즘 비교

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Component
public class LifecycleDemo implements InitializingBean, DisposableBean {
    // 생성자
    public LifecycleDemo() {
        System.out.println("1️⃣ 생성자 호출 - 객체 생성");
    }
    // JSR-250 어노테이션
    @PostConstruct
    public void postConstruct() {
        System.out.println("3️⃣ @PostConstruct - JSR-250 초기화");
    }
    // 스프링 인터페이스
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("4️⃣ afterPropertiesSet() - 스프링 인터페이스 초기화");
    }
    // JSR-250 어노테이션
    @PreDestroy
    public void preDestroy() {
        System.out.println("5️⃣ @PreDestroy - JSR-250 소멸");
    }
    // 스프링 인터페이스
    @Override
    public void destroy() throws Exception {
        System.out.println("6️⃣ destroy() - 스프링 인터페이스 소멸");
    }
}

예제 3: 실행 순서 확인용 테스트

1
2
3
4
5
6
7
8
9
10
@SpringBootTest
class BeanLifecycleTest {
    
    @Test
    void beanLifecycleOrder() {
        System.out.println("=== 스프링 컨텍스트 시작 ===");
        // 스프링 컨텍스트가 시작되면서 빈들의 생명주기가 시작됩니다
        // 로그를 통해 순서를 확인할 수 있습니다
    }
}

예상 로그 출력:

1
2
3
4
5
6
7
1️⃣ 생성자 호출 - 객체 생성
3️⃣ @PostConstruct - JSR-250 초기화
4️⃣ afterPropertiesSet() - 스프링 인터페이스 초기화
=== 스프링 컨텍스트 시작 ===
...
5️⃣ @PreDestroy - JSR-250 소멸
6️⃣ destroy() - 스프링 인터페이스 소멸

결론

스프링 빈 생명주기의 핵심은 “생성자에서는 의존성 사용 금지, @PostConstruct부터 안전하게 사용 가능”입니다. @PostConstruct로 초기화, @PreDestroy로 자원 정리만 확실히 알면 실무의 80% 문제를 해결할 수 있습니다.

댓글남기기