본문 바로가기

JAVA/Spring

[Spring] 빈 생명주기의 콜백

728x90

빈 생명주기 콜백은 스프링 빈이 생성되거나 종료될 때 빈 안의 메소드를 호출하는 것이다.

목차

  • 빈 생명주기 콜백 시작
  • 인터페이스 InitalizingBean, DisposableBean
  • 빈 등록 초기화, 소멸 메서드
  • 애노테이션 @PostConstruct, @PreDestory

 

빈 생명주기 콜백 시작

DBCP(데이터베이스 커넥션 풀), 네트워크 소켓 등 애플리케이션 시작 전 필요한 연결을 미리 하거나 종료될 때 안전하게 종료할 수 있는 작업을 진행하려면 객체의 초기화와 종료 작업이 필요하다.

예제로 어떻게 초기화와 종료 작업이 이루어지는지 알아보자.

public class NetworkClient {
    private String url;

    public NetworkClient() {
        System.out.println("생성자 호출, url = " + url);
        connect();
        call("연결 메시지");

    }

    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);
    }
}

외부 네트워크에 미리 연결하는 객체를 하나 생성하자. (예제에서는 실제로 네트워크에 연결하지 않고 문자만 출력시킴)

 

public class BeanLifeCycleTest {

    @Test
    public void lifeCycleTest() {
        ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
        NetworkClient client = ac.getBean(NetworkClient.class);
        ac.close();
    }

    @Configuration
    static class LifeCycleConfig {

        @Bean
        public NetworkClient networkClient() {
            NetworkClient networkClient = new NetworkClient();
            networkClient.setUrl("http://hello-spring.dev");
            return networkClient;
        }
    }
}

위를 실행시킬 때 원하는 동작은 생성자가 호출되며 url이 연결되어 "생성자 호출, url = " + http://hello-spring.dev가 출력이다.

 

생성자 호출, url = null
connect: null
call: null message = 연결 메시지

실제로 출력된 내용은 아래와 같다.

 

NetworkClient networkClient = new NetworkClient();
      networkClient.setUrl("http://hello-spring.dev");

예시 코드를 다시 보면 생성자를 호출하는 순간 이후에 설정이 들어오기 때문에 null로 출력된다.

 

객체 생성 → 의존관계 주입

스프링 빈은 위 와 같은 라이프사이클을 가짐

스프링 빈은 객체 생성과 의존관계 주입이 끝난 뒤에 데이터를 사용할 수 있는 준비가 된다.

그렇기 때문에 의존관계 주입이 끝남과 동시에 데이터를 사용하고 싶다면 콜백 메서드를 사용하여야 한다. 또한 스프링 컨테이너가 종료되기 직전에 소멸콜백을 보낸다.

 

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

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

 

 

  • 초기화 콜백 : 빈이 생성된 후, 의존관계 주입이 완료된 시점에 호출
  • 소멸전 콜백 : 빈이 소멸되기 직전에 호출

 

참고로 생성자를 통해 초기화, 설정 정보 등을 주입하지 않는 이유는 코드의 기능을 명확하게 역할을 나누기 위해서이다. (유지보수 관점에서 좋음) 초기화 작업이 사소한 값 변경 등과 같은 경우에는 생성자에서 한번에 처리하는게 더 나을 수도 있어서 상황에 맞추어서 사용하자.

 

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

  • 인터페이스(InitalizingBean, DisposableBean)
  • 설정 정보에 초기화 메서드, 종료 메서드 지정
  • @PostConstruct, @PreDestory 애노테이션 지원

1. 인터페이스 콜백

public class NetworkClient implements InitializingBean, DisposableBean {
    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);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        connect();
        call("초기화 연결 메시지");
    }

    @Override
    public void destroy() throws Exception {
        disconnect();
    }
}

기존 소스에서 InitializingBean, DisposableBean 인터페이스를 상속받았다.

상속과 함께 인터페이스 메소드를 구현하게 되는데 이 메소드가 빈 초기화와 종료 시 호출되는 메소드이다.

출력값

 

생성자 호출, url = null
connect: http://hello-spring.dev
call: http://hello-spring.dev message = 초기화 연결 메시지
17:18:01.138 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@ba8d91c, started on Thu Jul 15 17:18:00 KST 2021
close: http://hello-spring.dev

생성자 호출 다음 url이 대입이 되고, 생성 끝난 뒤 빈 종료에 destory 메소드가 생성된다.

 

초기화, 소멸 인터페이스의 단점

  • 스프링 전용 인터페이스이므로 스프링에 의존한다.
  • 초기화, 소멸 메서드의 이름을 변경할 수 없다.
  • 내가 코드를 고칠 수 없는 외부 라이브러리에는 사용할 수 없다.

스프링 초창기에 나온 방법이며 많이 사용하지 않는다.

 

2. 빈 등록 초기화, 소멸 메서드

빈 애노테이션에 직접 초기화, 소멸 메서드를 지정하는 것이다.

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);
    }

    public void init() {
        connect();
        call("초기화 연결 메시지");
    }

    public void close() {
        disconnect();
    }
}

인터페이스를 없애고 메소드를 init과 close로 바꾸었다.

 

@Test
    public void lifeCycleTest() {
        ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
        NetworkClient client = ac.getBean(NetworkClient.class);
        ac.close();
    }

    @Configuration
    static class LifeCycleConfig {

        @Bean(initMethod = "init", destroyMethod = "close")
        public NetworkClient networkClient() {
            NetworkClient networkClient = new NetworkClient();
            networkClient.setUrl("http://hello-spring.dev");
            return networkClient;
        }
    }

실행 클래스의 Bean에 initMethod와 destroyMethod에 메소드 이름을 지정해주면 완료

 

생성자 호출, url = null
connect: http://hello-spring.dev
call: http://hello-spring.dev message = 초기화 연결 메시지
17:38:34.479 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@ba8d91c, started on Thu Jul 15 17:38:34 KST 2021
close: http://hello-spring.dev

앞서 보았던 똑같은 출력값을 받을 수 있다.

 

설정 정보 사용 특징

  • 메서드 이름을 자유롭게 줄 수 있다.
  • 스프링 빈이 스프링 코드에 의존하지 않는다.
  • 코드가 아니라 설정정보에 들어가기 때문에 외부 라이브러리에도 세팅할 수 있다.

(@Bean의 destoryMethod은 default 인자값으로 close를 사용하고 있어서 따로 종료 메서드를 적지 않아도 close 혹은 shutdown 이름의 메서드를 호출한다. 추론 기능을 사용하기 싫다면 destoryMethod=""를 사용하여야 함)

 

3. 애노테이션을 이용한 콜백(PostConstruct, PreDestory)

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() {
        connect();
        call("초기화 연결 메시지");
    }

    @PreDestroy
    public void close() {
        disconnect();
    }
}

대상 메서드에 @PostConstruct, @PreDestory 를 달아주면 완료

 

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

두 애노테이션의 import package 경로를 보면 위와 같다.

javax는 자바에서 공식적으로 지원하는 api이므로 스프링이 아니여도 사용할 수 있다.

 

특징

  • 최신 스프링에서 권장하는 방법
  • 애노테이션 하나만 붙여도 되므로 매우 편리
  • 스프링에 종속적인 기술이 아닌 JSR-250이라는 자바 표준이다.
  • 컴포넌트 스캔과 잘어울린다.
  • 유일한 단점은 외부 라이브러리에 적용하지 못한다는 것이다.

 

정리

  • @PostConstruct, @PreDestory 애노테이션을 사용하면 된다.
  • 외부라이브러리에 사용하려면 빈 등록, 초기화 메소드를 사용하자.