[김영한님 스프링 핵심 원리] 빈의 생명 주기와 콜백

2022. 11. 2. 13:29Spring

728x90

먼저, 빈의 생명 주기 중 객체의 초기화와 종료 작업을 공부하기 위해,

애플리케이션에서 외부 네트워크에 연결하는 객체를 만들어야 한다고 가정해보자!

 

public class NetworkClient {

    private String url;

    public NetworkClient() {
        System.out.println("생성자 호출 = " + 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);
    }
}

 

그리고, 이를 Config클래스를 만들어, 스프링 컨테이너에서 이를 실행한다.

@Configuration
    static class LifeCycleConfig {

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

    }

 

그렇게 되면, 

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

 

아래와 같이 출력이 된다.

출력된 메세지를 통해서, 우리는 생성자가 호출된 후에 객체가 초기화가 되기때문에, null값으로 나왔다는 사실을 알 수 있다.

 

여기서 간단한 스프링 빈의 라이프 싸이클을 알 수 있는데, 

객체 생성 -> 의존관계 주입 순으로 진행된다는 것이다.

 

단, 생성자 주입은 예외!! 객체 생성함과 동시에 의존관계주입

 

스프링 빈은 객체를 생성하고, 의존관계를 주입한 다음에 초기화를 해야한다.

 

여기서 의문!! 스프링 빈생성과 의존관계 주입 그리고 초기화를 따로 진행할 필요가 있을까??

 

그 이유는 다음과 같다.

 

생성자의 경우에 필수 정보(파라미터)를 받고, 메모리를 할당해서 객체를 생성하는 역할을 가진다.

그리고 '초기화'는 이렇게 생성된 값들을 활용해서 외부 커넥션이랑 연결하는 무거운 동작을 수행하는 역할을 가진다.

이렇듯, 생성자와 초기화 각각의 역할들이 있어서, 이 역할별로 코드를 분리하는 게 유지보수 측면에서 좋다.

 

 

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

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

 

초기화 콜백, 소멸전 콜백 받는 법

 

@Bean 설정 정보 변경

public class NetworkClient {

    private String url;

    public NetworkClient() {
        System.out.println("생성자 호출 = " + 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 void init() {
        System.out.println("NetworkClient.init");
        connect();
        call("초기화 연결 메세지");
    }

    public void close() {
        System.out.println("NetworkClient.close");
        disconnect();
    }
}

 

init()메서드와 close()메서드를 만들어 초기화와 소멸하기 전 네트워크와의 연결을 끊을 것이다.

 

@Configuration
    static class LifeCycleConfig {

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

    }

위와 같이 설정정보를 이용해서 쉽게 콜백을 할 수 있다.

 

종료 메서드 @Bean의 destroyMethod 속성에는 특별한 기능이 있는데, 이는 destroyMethod의 기본 값은 (inferred) 추론으로 등록되어있다는 것이다.

 

이 기능은 close, shotdown라는 이름의 메서드를 자동으로 호출해준다.

따라서, 직접 스프링 빈으로 등록하면 종료 메서등는 따로 적어주지 않아도 된다.

 

이 방법의 특징은 다음과 같다.

  • 메서드 이름을 자유롭게 명명할 수 있다.
  • 스프링 빈이 스프링 코드에 의존하지 않는다.
  • 코드가 아니라 설정정보를 이용하기 때문에, 코드를 고칠 수 없는 외부 라이브러리에도 초기화,종료 메서드를 적용할 수 있다.

 

@PostConstruct, @PreDestroy

 

이 방법과 같은 경우에는 애노테이션만 붙여주면 된다.

 @PostConstruct
    public void init() {
        System.out.println("NetworkClient.init");
        connect();
        call("초기화 연결 메세지");
    }
    @PreDestroy
    public void close() {
        System.out.println("NetworkClient.close");
        disconnect();
    }

 

특징은 다음과 같다.

  • 자동 빈 등록 그러니깐 ComponnetScan 애노테이션과 잘 어울린다.
  • 자바 표준을 사용한다.
  • 스프링에서 권하는 방법이다.
  • 편리하다

 

이 방법의 단점은 직접 메소드 위에 애노테이션을 붙여주어야 하기때문에, 외부 라이브러리에는 적용하지 못하는 것이다.

 

 

정리하자면, @PostConstruct, @PreDestroy 애노이션을 사용하고 외부 라이브러리를 사용해야 할 때는 @Bean기능을 이용하도록 하자!

 

이상입니다~!

 

 

 

728x90