Spring

빈 생명주기 콜백

S2채닝S2 2025. 2. 4. 16:56

빈 생명주기 콜백

  • DBCP(Database Connection Poll)이나, Network Socket 처럼 애플리케이션 시작 시점에 필요한 연결을 미리 해두고, 종료 시점에 연결을 모두 종료하는 작업을 하려면 객체 초기화와 종료 작업이 필요하다.
  • 보통은 시작 시점에 객체 초기화를 위한 메서드를 호출하고, 종료 시점에 연결 종료를 위한 메서드를 호출한다.

스프링 빈 이벤트 라이프사이클

스프링 빈은 객체를 생성한 후 의존관계를 주입하고, 사용하는 라이프사이클을 가진다. 객체 생성 및 의존관계 주입까지 마쳐에 필요한 데이터를 사용할 수 있는 준비가 완료되는 것이다. 따라서 초기화 작업은 의존관계 주입이 완료된 후에 호출하게 된다. 이 완료 시점은 콜백 메서드를 통해 알 수 있다.
스프링은 의존관계 주입이 완료되면 스프링 빈에게 콜백 메서드를 통해서 초기화 시점을 알려주는 기능을 제공한다. 또한 스프링은 스프링 컨테이너가 종료되기 직전 소멸 콜백을 준다. 이를 통하여 안전하게 종료 작업을 진행할 수 있다

스프링 컨테이너 생성 → 스프링 빈 생성 → 의존관계 주입 → 초기화 콜백 → 사용 → 소멸 전 콜백 → 스프링 종료

  • 초기화 콜백: 스프링 빈 생성 및 의존관계 주입 완료 후 호출
  • 소멸 전 콜백: 스프링 빈이 소멸되기 직전 호출

객체 생성과 초기화 분리

생성자는 필수 정보(파라미터)를 받고, 메모리를 할당해서 객체를 생성하는 책임을 가진다. 반면 초기화는 생성된 값들을 활용하여 외부 커넥션 연결 등 무거운 동작을 수행한다. 따라서 생성자 안에서 무거운 초기화 작업을 하는 것 보다는 **객체를 생성하는 부분과 초기화하는 부분을 명확하게 나누는 것이 유지보수 관점에서 (훨씬) 좋다.

 

스프링 빈 생명주기 콜백

스프링은 크게 3가지 방법으로 빈 생명주기 콜백을 지원한다

  • 인터페이스 상속(InitializingBean, DisposableBean)
    • InitializingBean.afterPropertiesSet() 메서드로 초기화 지원
    • DisposableBean.destroy() 메서드로 소멸 지원
    • 인터페이스 상속 방식은 스프링 전용 인터페이스이며, 초기화 및 소멸 메서드를 상속받아야하기 때문에 메서드 이름을 변경할 수 없고, (내가 코드를 고칠 수 없는) 외부 라이브러리에 적용할 수 없다는 단점이 있다. 이 방법은 초창기에 사용되었고, 현재는 거의 사용하지 않는다.
  • 설정 정보에 초기화 메서드, 종료 메서드 지정
    • 설정 정보(Confuguration 클래스)에 @Bean(initMethod = "초기화 메서드명", destroyMethod = "소멸 메서드명" 과 같이 초기화 및 소멸 메서드를 메서드명을 기준으로 설정할 수 있다.
    • destroyMethod 는 기본값이 (inferred)(추론)으로, 메서드 명이 close 또는 shutdown인 메서드를 자동으로 호출해준다.
  • @PostConstruct, @PreDestroy 애노테이션
    • 생성 및 종료 메서드에 애노테이션을 붙여 사용한다.
    • 최신 스프링에서 가장 권장하는 방법이며, 애노테이션 하나만 붙이면 되므로 매우 편리하다.
    • 자바 표준 패키지이기 때문에 스프링 종속적이지 않고, 스프링이 아닌 다른 컨테이너에서도 동작한다.
    • 외부 라이브러리에는 적용하지 못하기 때문에 외부 라이브러리 적용 시에는 @Bean의 기능을 사용하면 된다.

 

@PostConstruct, @PreDestroy 코드

public class NetworkClient {

    private String url;

    public NetworkClient() {
        System.out.println("생성자 호출, url = " + url);
    }

    public void setUrl(String url){
        this.url = url;
    }

    // 서비스 시작 시 호출
    public void connect(){
        System.out.println("connect: " + url);
    }

    public void call(String message){
        System.out.println("call: " + url + " message = " + message);
    }

    // 서비스 종료시 호출
    public void disconnect(){
        System.out.println("close " + url);
    }

    // 의존관계 주입이 끝나면 호출
    @PostConstruct // 최신 스프링 권장
    public void init() throws Exception {
        // 기존에 생성자에 포함되었던 객체 초기화 부분을 가져온다. 생성과 초기화는 분리하는 것이 유지보수 측면에서 좋음.
        System.out.println("NetworkClient.init");
        connect();
        call("초기화 연결 메시지");
    }

    // 객체 소멸 시 호출
    @PreDestroy // 최신 스프링 권장
    public void close() throws Exception {
        System.out.println("NetworkClient.close");
        disconnect();
    }

정리

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