본문 바로가기
JAVA & SPRING/Spring 입문

스프링 핵심 원리 - 7일차(빈 생명주기 콜백)

by 눈오는1월 2023. 7. 31.
728x90

디비와 연동하거나 네트워크 소켓처럼 미리 연결을 해야지 바로바로 응답할 수 있고, 서버 다운될때 한번에 종료되는것보다 미리 안전하게 종료가 더 좋다고 함 이러한 기능을 스프링에서 어떻게 하는지 알아본다.

 

간단한게 네트워크 미리 연결하는 객체 예제를 통해 알아보면,

package hello.core.lifecycle;

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


}

해당 코드는 객체가 생성될때 생성자에서 생성 후 연결을 해준다.

package hello.core.lifecycle;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
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);
        ac.close();

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

테스트 코드 결과

위 결과는 당연히 생성자 호출할때는 객체 생성하는 단계니까 url 연결 없어서 null 값이 뜸

 

스프링 빈은 객체 생성 -> 의존관계 주입 이라는 라이프사이클을 가진다

초기화 작업은 의존관계 주입이 끝나고  해서 해야함 

객체의 생성과 초기화를 분리를 해줘야 한다.(이 방식이 단일책임 원칙에 더 준수함 생성은 생성만 담당)

만약 초기화 작업이 단순할경우 생성자에서 한번에 처리하는 게 나을 수 있지만 그렇게 하는 상황이 매우 많지는 않음

 

이렇게 빈 생멍주기 콜백하는것을 스프링에서는 3가지 방식을 지원해준다

인터페이스(InitializingBean, DisposableBean)

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

@PostConstruct, @PreDestroy 어노테이션

이렇게 3가지에 대해서 알아본다.

 

1. 인터페이스(InitializingBean, DisposableBean) 방식

package hello.core.lifecycle;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

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);
    }
    
    @Override
    public void afterPropertiesSet() throws Exception {
    	connect();
        call("초기화 연결 메세지");
    }
    
    @Override
    public void destory() throws Exception {
    	disConnect();
    }


}

테스트 출력 결과

InitializingBean에  afterPropertiesSet() 은 메서드 초기화를 지원하고

DisposableBean은 destory() 메서드 소멸을 지원함

 

초기화 메서드가 주입 이후에 적절하게 호출됨 소멸 메서드도 호출됨

-> 이러한 방식은 거의 잘 안쓰임

why? ) 스프링 전용 인터페이스에 의존하고 초기화, 소멸 메서드 이름 변경이 불가능하다. 그리고 외부 라이브러리에는 적용 할 수 없음

 

 

2.설정 정보 초기화 메서드, 종료 메서드 지정

이방법은 쉽다

@Bean(initMethod = "초기화메서드 이름", destroyMethod = "종료 메서드 이름" )이런식으로 적으면된다.

NetworkClient 클래스를 원래대로 바꾼후에 @Bean 옆에 위 에시처럼 추가한다.

package hello.core.lifecycle;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
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);
        ac.close();

    }
    @Configuration
    static class LifeCycleConfig {
        @Bean (initMethod = "init", destroyMethod = "close")
        public NetworkClient networkClient() {
            NetworkClient networkClient = new NetworkClient();
            networkClient.setUrl("heep://hello-spring.dev");
            return networkClient;
        }
    }
}
설정 정보 초기화 메서드, 종료 메서드 지정 테스트 결과

 

이건 1번 방식과 다르게 메서드 이름을 내가 정할 수 있고 스프링 코드에 의존하지 않아도 된다.

또 외부 라이브러리에서도 적용이 가능하다.

또한 위에 destroyMethod에는 재밌는 기능하나가 숨겨져 있다.

destoryMethod의 기본값은 interred(추론)으로 등록되어져있다. 이 기능은 close, shutdown 이름이 있으면 해당 메서드를 자동으로 호출해준다. 그래서 따로 적지 않아도 잘 동작함

만약 추론 기능을 사용하기 싫으면 destroyMethod=""처럼 빈 공백을 저장하면 된다.

 

3.@PostConstruct, @PreDestroy 어노테이션

최근 스프링이 가장 권장하는 방법이다.

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{

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

//    @Override
//    public void afterPropertiesSet() throws Exception {
//        connect();
//        call("초기화 연결 메세지");
//    }
//
//    @Override
//    public void destroy() throws Exception {
//        disconnect();
//    }



    @PostConstruct
    public void init() {  // 의존관계 호출이 끝나면
        System.out.println("NetworkClient.init");
        connect();
        call("초기화 연겲 메세지");

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


}

어노테이션 방식 테스트 결과

@Bean은 이제 다시 원상태로 돌린 후에 초기화 메서드 종료 메서드 위에 각각의 어노테이션을 붙이면 된다

위 패키지를 보면 javax로 적혀있는데 이건 자바에 공식적으로 지원이 되는거다. 그래서 스프링 뿐만 아니라 다른 컨테이너에서도 잘 동작함

단점은 외부 라이브러리에 적용을 못하는데 외부 라이브러리에 적용할때만 2번방식을 사용하면됨

 

 

728x90