[기본기] 8-1. 빈 생명주기 콜백, InitializingBean, DisposableBean

khyojun·2022년 10월 8일
0
post-thumbnail

본 게시글은 김영한님의 스프링 핵심 원리 기본편을 정리한 글입니다.


📌 빈 생명주기 콜백

빈 생명주기에 대해서 알아가는 시간이다. 이것을 왜 알아야 하는지부터 알고 가야될 거 같다. 예전에 한 번 언급을 했었던 거 같은데 이게 데이터베이스 커넥션이라든지 네트워크 소켓처럼 뭐 미리 연결을 하고 나중에 끊어내는 작업이 생각보다 되게 아주 중요한 문제라고 했었다. 그렇지 않으면 생각보다 되게 자원적으로 손해를 많이 보는 부분이 있어서 미리미리 처리를 잘 해주고 끝 마무리도 잘 끊어줘야 된다고 했었다. 그래서 이러한 종료나 초기화를 시켜주는 부분들이 스프링에서도 필요한데 어떻게 하는지 한 번 알아보자.

아래의 예제는 외부 네트워크에 미리 연결하는 객체를 하나 생성을 하고 애플리케이션 시작 지점에 connect()를 호출하여 연결하고 애플리케이션이 종료될 때는 disconnect()를 통하여 연결을 끊는 상황이다.

ublic 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 networkClient = ac.getBean(NetworkClient.class);
        ac.close();
    }

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

여기서 볼 부분이 close()라는 메서드를 사용하기위하여서는 ConfigurableApplicationContext를 사용을 하여야 한다. 스프링 컨테이너를 종료시키기 위한 메서드이다. 이 코드를 실행하면 과연 url이 연결이 되어질때 잘 나올까?

예상할 수 있는대로 지금 생성자에서 애초에 url을 넣어주지 않았으니 당연히 연결을 해줘도 null이 뜰 수밖에 없다. setUrl()을 통하여서 값을 넣어줘야 당연히 이제 값이 들어갈 거다. 근데 이런 출력물은 우리가 바라는 결과가 아니다. 우리는 이 호출할때부터 url이 들어가는 모습을 보고 싶다! 그러기 위하여서 우리가 빈 생명주기를 알아야 한다.

빈 생명주기는 다음과 같다.

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

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

그리고 여기서 또 알아야 할 것이 이제 강의에서 객체의 생성과 초기화를 분리하여야 한다고 한다. 왜?

생성자는 필수 정보(파라미터)를 받고, 메모리를 할당해서 객체를 생성하는 책임을 가진다. 그러나 초기화는 이렇게 생성된 값들을 활용해서 외부 커넥션을 연결하는 등 무거운 동작을 수행한다.
그래서 생성자에게 초기화와 생성을 담당하는 것을 둘 다 맡기기에는 초기화의 작업이 너무 무겁다. 그래서 이를 나누는 것이 유지 보수의 관점에서 보면 되게 좋다. 그치만 내부 값들만 약간 변경하여주는 경우에는 생성자에서 처리를 해주는 것이 편할 수도 있다.
이건 SRP가 약간 적용되는 부분인 거 같다.

그래서 이러한 부분들을 해결하여주기위하여서 스프링은 크게 3가지의 방법으로 빈 생명주기 콜백을 지원을 한다.

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

이렇게 있는데 우선 인터페이스로 하는 방법을 확인해보자.

InitializingBean, DisposableBean

코드를 통해 확인을 해보면

public class NetworkClient implements InitializingBean, DisposableBean {

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

    //DisposableBean 에서 끌고와야하는 메서드 종료 콜백
    @Override
    public void destroy() throws Exception {
        System.out.println("Close : NetworkClient.close()");
        disconnect();
    }

    //InitializingBean했을때 순간 초기화 콜백
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("connect: InitializingBean");
        connect();
        System.out.println("초기화 연결 메시지");
    }
}

위 코드를 보면 이제 InitializingBean -> afterPropertiesSet, DisposableBean -> destroy 이라는 메서드가 같이 딸려온다. 여기서도 음... 유연하지 않다는 느낌이 조금 든다. 이거 인터페이스를 쓰기 위해서 반드시 저 메서드를 사용해서 해야한다고 강제당하는 느낌.

afterPropertiesSet : 초기화를 지원한다.
destroy : 메서드의 소멸을 지원한다.

출력 결과를 보면

이전과는 다르게 url들이 다 들어간 것을 볼 수 있다. 위에서 잠깐 언급했지만 일단 단점이 확실하게 있어보이는 방법이다.

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

  • 해당 인터페이스는 스프링의 전용 인터페이스이기에 스프링 전용 인터페이스에 의존한다.
  • 초기화, 소멸 메서드의 이름을 변경할 수 없다
  • 내가 코드를 고칠 수 없는 외부 라이브러리에 적용을 할 수 없다.
    그리고 이보다 좋은 방법도 지금은 많이 있다고 한다. ㅎㅎ ^^;

오늘의 결론

빈 생명주기를 알아야 한다는 점에 대해서 다시 한 번 알고 가고 InitializingBean, DisposableBean 이라는 것의 단점을 잘 알아가자.

출처

  1. 김영한님의 스프링 핵심 원리 기본편(https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8)
profile
코드를 씹고 뜯고 맛보고 즐기는 것을 지향하는 개발자가 되고 싶습니다

0개의 댓글