백엔드/Spring

[Spring] 스프링 생명주기와 초기화, 종료 콜백

happy_life 2022. 5. 18. 21:57

목차

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

2. Bean 객체 예제

3. 초기화 종료 콜백

 

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

1. 스프링 컨테이너 생성 -> 2. 스프링 빈 생성 -> 3. 의존관계 주입 -> 4. 초기화 콜백 -> 5. 소멸전 콜백 -> 6. 스프링 종료

 

스프링은 빈 객체를 생성하고 의존관계 주입이 다 끝난 뒤에야 필요한 데이터를 사용할 준비가 완료된다. 따라서 개발자는 의존관계 주입이 모두 완료된 시점을 알 수 있어야 한다. 운이 좋게도, 스프링은 초기화, 종료 콜백을 통해 의존관계 주입이완료되었다는 것을 알려준다. 

 

2. Bean 객체 예제

 

NetworkClient

public class NetworkClient {

    private String url;

    public NetworkClient() {
        System.out.println("생성자 호출 : " + url);
        connect();
        call("초기화 연결 메시지");
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUrl() {
        return url;
    }

    public void call(String message) {
        System.out.println("call: " + url + ", message = " + message);
    }

    public void connect() {
        System.out.println("connect: " + url);
    }

    public void disconnect() {
        System.out.println("close: " + url);
    }
}

BeanLifeCycleTest

public class BeanLifeCycleTest {

    @Test
    public void lifeCycleTest() {
        AnnotationConfigApplicationContext 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이 없음을 알 수 있다.

 

이는 생성자 호출 -> 초기화를 끝낸 후에만 client의 객체에 url이 있는 것이다.

코드 동작을 크게 나눠보면 아래의 2단계로 구분할 수 있다. (참고)

 

Q) 초기화를 하는 부분을 굳이 나누지 않고, 생성자를 넣는 시점에 해주면, 굳이 초기화 시점을체크하지 않아도 되는 거아닐까요? 

 

생성자는 필수 정보를 받고, 메모리를 할당해서 객체를 생성하는 책임을 가집니다. 반면 초기화는 생성된 값들을 활용해 외부 커넥션을 연결하는 등 무거운 동작을 수행합니다. 따라서 생성자 안에서 초기화작업을 함께하기보단 생성/초기화를 명확하게 구분하는 것이 유지보수 관점에서 좋습니다. ( 간단한 경우는 한번에 처리하는 게 더 낫기도 함)

 

 

3. 초기화 종료 콜백

초기화 종료 콜백 방법

방법 1. InitializingBean, DisposableBean

InitializingBean을 Override한 afterPropertiesSet, DisposableBean을 Override하 destory가 초기화와 종료 시 콜백되어 시점을 알려줍니다.

Bean 등록 과정의 생성/초기화가 일어난 이후 호출되는 것이기 때문에 connect에 값이 들어가있는 것을 알 수 있습니다.

 

출력결과

Bean 등록 과정의 생성/초기화가 일어난 이후 호출되는 것이기 때문에 connect에 값이 들어가있는 것을 알 수 있습니다.

 

하지만 이 방법은 스프링 전용 인터페이스에 의존하므로,

1.     초기화 소멸 메소드의 이름을 변경할 수 없다.

2.     내가 고칠 수 없는 외부 라이브러리에 적용할 수 없다.

는 단점이 있습니다.( 최근엔 사용하지 않습니다.)

 

방법 2. 설정정보 사용하기

@Bean(initMethod = "init", destroyMethod = "close")

1. Bean 설정정보 등록시 아래와 같이 코드를 추가합니다.

2. 이후 기존의 Override 부분과 인터페이스 부분을 삭제하고 아래와 같이 코드를 입력합니다. init(), close()

public class NetworkClient  {

    private String url;

    public NetworkClient() {
        System.out.println("생성자 호출 : " + url);
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUrl() {
        return url;
    }

    public void call(String message) {
        System.out.println("call: " + url + ", message = " + message);
    }

    public void connect() {
        System.out.println("connect: " + url);
    }

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

    public

출력결과

2번째 방법은 1번째 방법과 달리

1. 메서드의 이름을 자유롭게 바꿀 수 있습니다.
2. 스프링코드에 의존하지 않습니다.

3. 설정정보를 사용하기때문에외부라이브러리에 초기화, 종료메서드를 적용할 수 있습니다.

 

 

방법 3. @PostConstruct,@PreDestroy 어노테이션 사용

 

1. 기존의 (initMethod) 등의 코드를 적지 않아도 됩니다.

2. 대신 아래와 같이 어노테이션을 적어줍니다.

이 방법의 특징

1. 최신 스프링이 가장 권하는 방법

2. 애노테이션 하나만 붙이므로 매우 편리

3. 스프링에 종속되지 않는 자바 표준이고, 따라서 다른 컨테이너에서도 동작

4. 수정할 수 없는 외부 라이브러리엔 적용하지 못함.