6. 빈 생명주기 콜백

2025. 4. 14. 16:40Spring/Core

1. 스프링 빈 생명주기 흐름 

1. 스프링 컨테이너 생성
2. 컴포넌트 스캔 및 BeanDefinition 등록
3. 객체 생성 및 의존성 주입
4. 초기화 콜백
5. 빈 사용
6. 스프링 컨테이너 종료 시, 소멸 콜백
7. 빈 소멸, 스프링 컨테이너 종료

1) 스프링 컨테이너 생성

new AnnotationConfigApplicationContext(설정정보.class)

 

2) 컴포넌트 스캔 및 빈 등록(BeanDefinition 등록)

  • 설정정보 클래스에 @ComponentScan이 붙어 있으면, 스프링이 클래스패스를 뒤져서 @Component, @Service, @Repository, @Controller 등 스캔 대상이 되는 클래스들을 찾아냄.
  • 찾은 클래스들은 BeanDefinition이라는 이름표(설계도) 형태로 스프링 컨테이너의 빈 저장소에 저장됨
    이건 아직 객체를 생성한 건 아니고, 클래스의 메타정보를 기반으로 “어떤 클래스를, 어떤 이름으로, 어떤 방식으로 빈으로 만들 건지”에 대한 정의만 저장된 상태.

3) 객체 생성 및 의존성 주입

  • 정의된 빈을 실제 객체로 만들고 생성자, setter, field 등에 붙어있는 @Autowired를 보고 의존성을 주입함.
    이때, 어떤 방식으로 의존성을 주입하는지에 따라 라이프 사이클이 달라진다! 
  • 생성자로 의존관계 주입하는 경우: 객체의 생성, 의존관계 주입이 동시에 일어남
  • setter, field로 의존관계 주입하는 경우: 객체의 생성 -> 의존관계 주입으로 라이프 사이클이 나누어져 있음
@Component
public class OrderService { //1. 생성자 주입 
    private final MemberRepository memberRepository;
    //의존관계가 없으면 애초에 객체 생성 자체가 안 됨. 
    @Autowired 
    public OrderService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}

@Component
public class OrderService { //2. setter 주입 
    private MemberRepository memberRepository;
    //일단 객체 생성 되고 -> 이후에 의존관계 자동주입됨  
    @Autowired 
    public void setMemberRepository(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}

4) 초기화 콜백 

  • 객체 생성과 의존성 주입까지 모두 완료되면 이제 객체를 사용할 준비는 됐고, 이후에 초기 셋업을 하는 단계가 초기화 단계이다.
  • 객체의 생성과 초기화를 분리하는 것이 권장되는 이유
    • 생성자는 필수 정보(파라미터)를 받고메모리를 할당해서 객체를 생성하는 책임을 가진다.
    • 반면에 초기화에서는 이렇게 생성된 값들을 활용해서 DB연결과 같은 외부 커넥션을 연결하거나, 캐시 초기화, 설정값 로딩과 같은 무거운 동작을 수행한다.
    • 따라서 생성자 안에서 무거운 초기화 작업을 함께 하는 것 보다 객체를 생성하는 부분과 초기화 하는 부분을 명확하게 나누는 것이 유지보수 관점에서 좋다
    • 물론 초기화 작업이 내부 값들만 약간 변경하는 정도로 단순한 경우 에는 생성자에서 한번에 다 처리하는게 더 나을 수 있다.

5) 빈 사용

6) 스프링 컨테이너 종료 시, 소멸 콜백

  • 스프링은 스프링 컨테이너가 종료되어 빈이 소멸되기 전에 소멸 콜백을 통해 프로그램이 안전하게 종료 될 수 있도록 한다.
  • DB 연결 끊기, 쓰레드 종료, 파일 닫기와 같은 뒷정리를 할 수 있는 기회를 줘야 리소스 누수나 장애를 막을 수 있기 때문이다. 

7) 빈 소멸, 스프링 컨테이너 종료 

 

2. 스프링의 초기화/소멸 콜백 지원 방법 

1) 스프링이 초기화/소멸 콜백을 지원하는 이유 

  • 스프링 빈의 초기화 작업은 객체를 생성하고, 의존관계 주입이 다 끝난 다음에 호출되어야 하고,
    소멸 작업은 애플리케이션이 종료되거나 컨테이너가 내려갈 때 호출되어야 한다.
  • 그런데 개발자가 초기화/소멸 작업을 직접 타이밍 맞춰 호출하는 건 번거롭고 오류가 나기 쉽다. 
  • 따라서 스프링은 객체 생성 및 의존성 주입 끝난 딱 그 순간, 컨테이너 종료 직전 딱 그 타이밍, 이 두 순간을 정확히 캐치해서 개발자가 정의한 초기화, 소멸 메서드를 자동으로 콜백해주는 기능을 제공한다. 

2) 스프링의 초기화/소멸 콜백 지원 방법 3가지 

1. 인터페이스(InitializingBean, DisposableBean)
2. 설정 정보에 초기화/소멸 메서드 지정
3. @PostConstruct, @PreDestroy 애노테이션 지원

 

코드로 예시 

public class NetworkClient {
    private String url;
    public NetworkClient() {
        System.out.println("생성자 호출, url = " + url); 
    }
    public void setUrl(String url) {
    	this.url = url;
    }
}
public class BeanLifeCycleTest {
    @Test
    public void lifeCycleTest() {
        ConfigurableApplicationContext ac = 
        		new AnnotationConfigApplicationContext(LifeCycleConfig.class);
        NetworkClient client = ac.getBean(NetworkClient.class); 
        ac.close(); //스프링 컨테이너 종료, ConfigurableApplicationContext 필요
    }
    
    @Configuration
    static class LifeCycleConfig {
        @Bean //수동 빈 등록
        public NetworkClient networkClient() {
            NetworkClient networkClient = new NetworkClient(); //객체 생성, 이때는 아무런 값X
            networkClient.setUrl("http://hello-spring.dev"); //값 설정 
            return networkClient; 
        }
    } 
}

 

방법 1. 인터페이스(InitializingBean, DisposableBean)

public class NetworkClient implements InitializingBean, DisposableBean {
    ... 
    @Override
    public void afterPropertiesSet() throws Exception {
    	//초기화 작업 수행 
    }
    @Override
    public void destroy() throws Exception {
    	//소멸 전 정리 (clean-up) 작업 수행
    }
}

 

방법 2. 설정 정보에 초기화/소멸 메서드 지정

public class NetworkClient{
	...
    public void init(){
    	//초기화 작업 수행 
    }
    public void destroy(){
    	//소멸 전 정리 (clean-up) 작업 수행
    }
}

@Configuration
static class LifeCycleConfig {
    @Bean(initMethod = "init", destroyMethod = "close")
    public NetworkClient networkClient() {
        return new NetworkClient();
    } 
}

 

방법 3. @PostConstruct, @PreDestroy 애노테이션 지원

public class NetworkClient{
	...
    @PostConstruct
    public void init(){
    	//초기화 작업 수행 
    }
    @PreDestroy
    public void destroy(){
    	//소멸 전 정리 (clean-up) 작업 수행
    }
}

@Configuration
static class LifeCycleConfig {
    public NetworkClient networkClient() {
        return new NetworkClient();
    } 
}
 

3가지 방법 비교 

방법 설명 장점 단점
InitializingBean,
DisposableBean
인터페이스 구현 명확함 스프링 전용, OOP에 불리함,
외부 라이브러리에 적용 못함 
초기화/소멸 메서드 지정 XML/JavaConfig에서 명시 외부 라이브러리에도 가능 설정에 의존
@PostConstruct,
@PreDestroy
표준 애노테이션 가장 직관적, 많이 쓰고 권장됨
스프링종속X 자바표준임
외부 라이브러리에 적용 못함

 

결론 

  • @PostConstruct, @PreDestroy 애노테이션을 사용하자
  • 코드를 고칠 수 없는 외부 라이브러리를 초기화, 종료해야 하면 @Bean의 initMethod, destroyMethod 를 사용하자