빈 생명주기 콜백

byeol·2022년 12월 2일
0

8. 빈 생명주기 콜백

💡 개념 정립하기

시작시점에 미리 연결해두고 종료시점에 연결을 모두 종료

예제는
NetworkClient가 애플리케이션 시작 시점에 connect()를 호출해서 연결 맺어두고 애플리케이션 종료되면 disconnect()를 호출해서 연결 끊기

NetworkClient

package hello.core.lifecycle;

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

스프링 환경설정과 실행

package hello.core.lifecycle;

import org.junit.jupiter.api.Test;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.Lifecycle;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

public class BeanLifecycleTest {


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

    @Configuration

    static class LifeCycleConfig {

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

}


위와 같은 결과가 나온다. 일단 그 이유를 살펴보기 위해서
먼저 그림으로 정리해보았다.


그림을 보면 우리는 생성자를 호출하는 Bean을 만들었고 거기 안에 초기화 작업을 넣어버렸디.
그런데 스프링 빈의 라이프 사이클은
객체를 생성 ➡️ 의존관계를 주입 ➡️ 초기화 작업 ➡️ 사용 ➡️ 소멸전 콜백 ➡️ 스프링 종료
이다.

따라서 위와 같이 Bean안에 초기화 작업을 넣었다 하더라도 초기화 작업은 뒤늦게 일어나기 때문에 바로 사용할 수 없다.

위 코드의 결과는 위 그림과 같은데 getBean을 통해서 객체를 생성했으나 초기화 작업이 완료되지 않았기 때문에 connect 다음에 url값이 아닌 null이 나온다.
하지만 종료되기 전에 client.connect()를 호출하니 초기화 작업이 완료되어 지정한 url값이 나왔다.

따라서 개발자는 언제 초기화 작업이 일어나는지 그 때는 알아야 언제 사용할 수 있는지를 알게되는 것이다.

스프링은 이 언제를 알려준다.
의존관계 주입 완료 후 콜백 메서드를 통해서 초기화 시점을 알려주고
종료되기 직전에 소멸 콜백을 준다.

여기서 우리가 배운 것은
객체의 생성과 초기화를 분리해야 한다는 것이다.
저렇게 bean에 객체 생성과 초기화를 분리하지 않는다면 유지보수 과정이 복잡하다.

💡Spring이 제공하는 빈 생명주기 콜백 3가지 방법

  • 1번 인터페이스 InitializingBean, DisposableBean

    NetworkClient를 위 두개의 인터페이스를 구현하고
    위 인터페이스의 메소드를 오버라이딩하는 방법으로 수정해보자.

    package hello.core.lifecycle;
    
    import org.springframework.beans.factory.DisposableBean;
    import org.springframework.beans.factory.InitializingBean;
    
    import javax.annotation.PostConstruct;
    import javax.annotation.PreDestroy;
    
    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();
       }
    }
    


    결과를 보면

    bean에 객체 생성과 초기화 작업이 같이 되어져 있지만
    인터페이스가 의존관계 주입 완료 후 적절한 시기에 연결하여 초기화 작업을 실행해주기 때문에 url에 설정이 제대로 되었다.

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

  • 스프링 전용 인터페이스 -> 스프링 코드에 의존
  • 초기화, 소멸 메소드의 이름 변경 불가 -> 오버라이딩하기 때문에
  • 내가 고칠 수 없는 외부 라이브러리에 적용 불가
  • 2번 설정 정보에 초기화 메서드, 종료 메서드

    NetworkClient에서 인터페이스 오버리이딩된 부분을 모두 지우고 아래와 같이 수정한다.

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

    후에 Bean에 아래와 같이 설정 정보를 추가한다.

    실행하면 아래와 같은 결과가 나온다.
    초기화 작업을 적절한 시기에 알맞게 해주고 있음을 확인할 수 있다.

    설정정보 사용의 특징
    - 메서드 이름 내 마음대로
    - 스프링 빈에 의존하지 않음
    - 외부라이브러리에도 초기화 종료 메서드 사용가능

  • 3번 @PostConstruct, @PreDestroy 애노테이션 지원

    NetwortClient를 아래와 같이 수정한다.

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

    빈을 아래와 같이 수정한다. 그 이전에 설정정보를 추가로 기재하지 않아도 된다.

    그 결과는 아래와 같다.

@PostConstruct, @PreDestroy를 사용하자

  • 스프링에 종속적인 기술이 아님
  • 편리하다
  • 유일한 단점-> 외부 라이브러이에 적용하지 못함
profile
꾸준하게 Ready, Set, Go!

0개의 댓글