본문 바로가기
Spring/스프링 핵심 원리 - 기본편

8-1. 빈 생명주기 콜백

by hk27 2022. 1. 10.
빈 생명주기 콜백은 무엇이고, 왜 필요한가요?

 

안녕하세요. 오늘은 빈 생명주기 콜백에 대해서 알아보겠습니다.

 

오늘의 주제인 '빈 생명주기 콜백'이 무엇인지 먼저 알아봅시다. 

스프링 빈의 '생명주기'는 스프링 빈이 만들어지고, 소멸되는 과정을 의미하는 것이겠죠.

 

콜백

그런데 '콜백(call back)'은 뭘까요? 

call back보다, call after라고 생각하면 이해하기 쉽습니다[2]. 

어떤 이벤트가 발생하면, 그다음으로 콜백 함수가 호출됩니다. 

저희가 일반적으로 함수를 사용할 때는, 코드에서 호출해서 부릅니다.

그런데 어떤 함수는 어떤 이벤트가 발생했거나 특정 시점에 도달했을 때 시스템에서 호출합니다[3].

이것이 콜백함수입니다. 

 

콜백 함수의 중요한 특징 중 하나는, 다른 함수에 매개변수로 넘겨진다는 것입니다[4]. 콜백을 넘겨받는 코드는 적절한 시점에 콜백 함수가 호출되도록 설정합니다. 아래는 위키피디아의 설명입니다. 

프로그래밍에서 콜백(callback) 또는 콜애프터 함수(call-after function)는 다른 코드의 인수로서 넘겨주는 실행 가능한 코드를 말한다. 콜백을 넘겨받는 코드는 이 콜백을 필요에 따라 즉시 실행할 수도 있고, 아니면 나중에 실행할 수도 있다.

 

빈 생명주기 콜백

그렇다면, 오늘 알아볼 빈 생명주기 콜백은 무엇일까요?

스프링 빈의 생명 주기를 알아봅시다.

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

스프링 컨테이너가 스프링 빈을 생성하고 의존 관계를 주입한다는 사실은 지금까지 알고 있던 사실입니다.

그런데 의존 관계 주입 다음으로 초기화 콜백을 거치고, 다음으로 스프링 빈을 코드에서 사용하고, 그다음 소멸전 콜백이 불리고, 스프링이 종료됩니다.

초기화 콜백은 스프링 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출되는 것이고,

소멸전 콜백은 빈이 소멸되기 직전에 호출되는 것입니다. 

즉 의존 관계가 주입된 다음, 초기화 콜백이 불리며 사용이 끝나고 스프링이 종료될 것이라는 신호가 온 다음, 소멸전 콜백이 불리는 것입니다. 

 

그렇다면 이런 초기화 콜백과 소멸전 콜백은 왜 필요할까요?

데이터베이스나 네트워크 소켓에 연결하는 작업은 애플리케이션이 시작될 때 초기화해두어야 하고, 애플리케이션이 끝나기 전에 종료해야 합니다.

이러한 초기화와 종료 작업이 어떻게 진행되는지 예제로 알아봅시다.

네트워크에 연결하는 객체 NetworkClient가 있다고 생각해봅시다. 애플리케이션이 시작될 때 connect()를 호출해서 연결을 맺어두어야 하고, 애플리케이션이 종료되면 disconnect()를 호출해서 연결을 끊어야 합니다.

예제 코드를 봅시다. 

 

NetworkClient 클래스

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);
        client.disconnect();
        ac.close();
    }

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

결과

생성자 호출, url = null
connect: null
call: null message = 초기화 연결 메시지
close: http://hello-spring.dev

코드의 흐름을 보면 생성자에서 connect, call 함수를 부른 뒤, setter 주입으로 의존관계 url을 주입하고, disconnect를 호출한 뒤, 스프링 컨테이너를 종료합니다.

결과를 보면, 생성자를 호출할 때는 url 의존관계가 주입되지 않아서 connect와 call이 제대로 수행되지 못한 것을 알 수 있습니다. 따라서 의존 관계가 주입된 다음에, connect와 call을 불러줘야 합니다.

 

저는 처음에 이 내용을 보고 '위의 예시처럼 setter 주입을 하지 않고 생성자 주입을 하면 생성자에서도 url을 사용할 수 있지 않나?'라는 생각이 들었습니다. 다른 수강생분이 질문을 해주셔서, 답변을 아래에 첨부합니다[5].

생성자만으로 100% 설정이 가능하지 않을 수도 있고, 필수 값이 아닌 경우 몇 가지 고민이 될 수 있습니다.

그렇죠. 생각해보면 의존 관계가 불변이나 필수가 아닐 수 있고, 그럴 때는 setter 주입을 써야 합니다.

그리고 생성자 주입을 사용해도, connect와 call은 이후에 따로 불러주는 것이 좋습니다.

왜냐하면, 객체의 생성과 초기화는 분리하는 것이 좋기 때문입니다.

 

객체의 생성과 초기화를 분리하자 

객체의 생성은 생성자 메소드를 불러서 필수 정보(파라미터)를 받고, 메모리를 할당해서 객체를 생성하는 것이고,

초기화는 생성된 값을 활용하여 외부 커넥션을 연결하는 등의 과정을 의미합니다. 사용하려는 객체를 사용 준비가 완료된 상태로 만드는 과정입니다[6]. 

초기화 작업이 내부 값들만 약간 변경하는 정도로 단순한 경우에는 생성자에서 한 번에 객체의 생성과 초기화를 수행할 수 있지만, 그렇지 않다면 객체의 생성과 초기화를 분리하는 것이 좋습니다.

초기화 작업은 무거운 작업이기 때문에 유지보수 관점에서도 바람직하고, SRP(단일 책임 원칙)도 지킬 수 있습니다. 

 

빈 생명주기 콜백이 필요한 이유

따라서 위의 네트워크 연결 사례도 객체를 생성한 다음, 의존 관계가 주입되고 초기화 작업을 수행하는 것이 좋습니다.

그렇다면, 클라이언트 코드에서 객체를 생성한 다음 초기화 함수를 불러주면 되겠죠. 

그런데, 그렇게 하기엔 클라이언트 코드에서 너무 번거로울 것입니다. 심지어 애플리케이션이 끝나기 전에 소멸 함수도 불러줘야 합니다.

그리고 클라이언트 코드에서 실수로 초기화와 소멸 함수를 부르지 않으면 코드가 작동하지 않을 것입니다. 

초기화와 소멸함수를 클라이언트에서 부르지 않고 자동으로 부를 수 있다면 좋겠죠.

이를 지원하는 것이 초기화 콜백과 소멸전 콜백(빈 생명주기 콜백)입니다. 

 

앞서 살펴본 스프링 빈의 이벤트 라이프사이클을 다시 봅시다. 

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

스프링 빈이 생성되고 의존 관계가 주입된 다음, 자동으로 초기화 콜백이 불립니다. 그리고 스프링 컨테이너가 종료하기 전에 자동으로 소멸전 콜백이 불립니다.

스프링은 크게 3가지 방법으로 초기화 콜백과 소멸전 콜백(빈 생명주기 콜백)을 지원합니다.

- 인터페이스(Intializing Bean, Disposable Bean)

- 설정 정보에 초기화 메서드, 종료 메서드 지정

- @PostConstruct, @PreDestroy 애노테이션 지원.

 

게시글이 길어져서 3가지 방법에 대한 구체적인 내용은 아래 게시글로 분리하였습니다. 

https://passionate.tistory.com/25

 

8-2. 빈 생명주기 콜백을 지원하는 3가지 방법

Q. 빈 생명주기 콜백은 어떻게 사용하나요? A. @PostConstruct, @PreDestroy 어노테이션을 붙이면 됩니다. 안녕하세요. 아래 게시글에서 빈 생명주기 콜백이 무엇인지와 왜 필요한지를 알아보았습니다. ht

passionate.tistory.com

 

인프런  '스프링 핵심 원리 - 기본편' 강의를 듣고 공부하며 정리한 자료입니다. 

잘못된 부분은 피드백 주시면 감사하겠습니다. 

글 읽어주셔서 감사합니다 :-)

 

참고자료

[1] 스프링 핵심 원리 - 기본편, 섹션 8. 빈 생명주기 콜백 https://www.inflearn.com/course/스프링-핵심-원리-기본편

[2] https://stackoverflow.com/questions/824234/what-is-a-callback-function

[3] https://www.hanumoka.net/2018/10/24/javascript-20181024-javascript-callback/

[4] https://ko.wikipedia.org/wiki/%EC%BD%9C%EB%B0%B1

[5] https://www.inflearn.com/questions/267624

[6] https://blog.naver.com/jlky1/222545990782

 

 

 

 

 

댓글