jhhan의 블로그

스프링 코어(14-1) - 빈 생명주기 콜백 - 3가지 방식 본문

Spring

스프링 코어(14-1) - 빈 생명주기 콜백 - 3가지 방식

jhhan000 2021. 8. 16. 16:52

이전 포스트에서 말한대로 빈 생명주기 콜백을 지원하는 3가지 방식에 대해 알아보겠습니다.

 

 

스프링은 크게 3가지 방식으로 빈 생명주기 콜백을 지원합니다.

  • 인터페이스(InitializingBean, DisposableBean)
  • 설정 정보에 직접 추가
  • 애노테이션(@PostConstruct, @PreDestroy)

 

 

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

인터페이스를 이용하는 방식입니다.

먼저 NetworkClient 클래스입니다.

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("disconnect : " + url);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("NetworkClient.afterPropertiesSet");
        connect();
        call("초기화 연결 메세지");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("NetworkClient.destroy");
        disconnect();
    }
}

InitializingBean과 DisposableBean 인터페이스를 이용하는 방식입니다.

  • InitializingBean은 afterPropertiesSet() 을 오버라이딩
  • DisposableBean은 destroy() 을 오버라이딩 해서 진행합니다.

 

그리고 BeanLifeCycleTest 클래스를 실행해 봅니다.

  • 생성자 호출을 할 때는 역시 url이 null입니다.
  • 하지만 connect() 를 호출할 때부터는 다릅니다.
  • setUrl()로 설정한 url값이 나오기 시작합니다.
  • afterPropertiesSet() 을 통해 초기화를 지원해준다는 것을 알 수 있습니다.
  • destroy() 를 통해 소멸 직전에 작동한다는 것을 알 수 있습니다.

이렇게 인터페이스를 이용해서 진행할 수 있습니다.

 

하지만 인터페이스를 이용하는 방식은 몇 가지 단점이 있습니다.

  1. 인터페이스가 스프링 전용 → 스프링에 전적으로 의존하게 됨
  2. afterPropertiesSet(), destroy() 라는 이름으로 고정 → 메서드 이름을 바꾸고 싶어도 바꿀 수 없음
  3. 외부 라이브러리에 적용X

이렇게 있습니다.

참고로 인터페이스를 이용하는 방식은 스프링 초창기에 나온 방법입니다.

따라서, 요즘은 잘 사용하지 않는 방식입니다.

이런 것이 있다라고 알아두면 좋을 것 같습니다.

 

 

2. 설정 정보에 직접 추가

설정 정보에 초기화 메서드, 소멸 메서드를 직접 추가하는 방식입니다.

바로 코드로 살펴보겠습니다.

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("disconnect : " + url);
    }

    public void init() throws Exception {
        System.out.println("NetworkClient.init");
        connect();
        call("초기화 연결 메세지");
    }

    public void close() throws Exception {
        System.out.println("NetworkClient.close");
        disconnect();
    }
}

1번에서 추가한 인터페이스는 제거합니다.

그리고 init(), close()라는 이름으로 변경해서 진행합니다.

 

다음은 BeanLifeCycleTest 클래스입니다.

public class BeanLifeCycleTest {

    @Test
    public void lifeCycleTest() {
//        ApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);        : ApplicationContext는 close() 메소드를 제공하지 않음
        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("http://spring-core.cycle");
            return networkClient;
        }
    }
}

@Bean 애노테이션 옆에 옵션을 주면 됩니다.

  • initMethod: 아까 init으로 설정했으니 그 이름을 넣습니다.
  • destoryMethod: 아까 close로 설정했으니 그 이름을 넣습니다.
  • 사실 옵션 이름만 봐도 대충 눈치챌 수 있습니다.

이제 실행해봅니다.

역시 동일하게 잘 동작합니다.

설정 정보를 직접 추가하면서 다음과 같은 이점이 있습니다.

  1. 메서드 이름을 자유롭게 설정 가능
  2. 스프링 코드에 의존X
  3. 설정 정보를 이용하기 때문에 외부 라이브러리에도 적용가능
  4. 모두 인터페이스에서의 단점을 개선한 것으로 볼 수 있습니다.

그리고 추가된 기능이 있습니다.

destroyMethod 옵션이 없어도 동작한다는 것입니다.

실제로 destroyMethod 옵션만 지우고 실행해봐도 결과는 똑같이 나오는 것을 알 수 있습니다.

어떻게 이럴 수 있을까요?

  • 대부분의 종료 메서드는 close, shutdown 같은 이름을 사용합니다.
  • 그래서 옵션에 추가하지 않아도
  • close나 shutdown의 이름을 가진 메서드를 자동으로 호출합니다.
  • 그래서 종료 메서드가 동작하는 것입니다.
  • 만약 destroyMethod 옵션이 동작하기를 원하지 않는다면 destoryMethod=""로 하면 동작하지 않습니다.

실제로 동작하지 않는군요.

 

이 방법에도 단점이 있습니다.

바로 개발자가 직접 입력하기 때문에 개발자가 실수하면 에러가 발생한다는 점입니다.

그래서 보통은 3번째를 많이 사용한다고 합니다.

 

 

3. 애노테이션(@PostConstruct, @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("disconnect : " + url);
    }

    @PostConstruct
    public void init() throws Exception {
        System.out.println("NetworkClient.init");
        connect();
        call("초기화 연결 메세지");
    }

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

    @Test
    public void lifeCycleTest() {
//        ApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);        : ApplicationContext는 close() 메소드를 제공하지 않음
        ConfigurableApplicationContext 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://spring-core.cycle");
            return networkClient;
        }
    }
}
  • 애노테이션을 추가하면 됩니다.
  • @PostConstruct : 대충 생성자 이후 라는 의미입니다. 생성자 이후에 동작하는 것을 추론할 수 있습니다.
  • @PreDestroy     : 대충 소멸 전 이라는 의미입니다. 소멸 전에 동작하는 것을 추론할 수 있습니다.
  • 그리고 @Bean에는 옵션을 주지 않습니다.

그리고 실행한다면 역시 같은 결과가 나온다는 것을 알 수 있습니다.

아까 설정정보를 추가하는 것보다 훨씬 간편하네요!

애노테이션의 패키지

장점.

  • 애노테이션만 붙이면 되기 때문에 굉장히 편리합니다.
  • 애노테이션의 패키지를 보면 javax로 시작합니다. → 스프링에 의존하지 않습니다.
  • 그래서 스프링이 아닌 다른 곳에서도 잘 동작합니다.
  • 컴포넌트 스캔과 같이 사용해도 문제가 없습니다.
  • 스프링에서도 애노테이션 사용을 권장합니다.

단점.

  • 외부 라이브러리에는 적용불가 → 유일한 단점입니다.
  • 혹시 외부 라이브러리에 적용을 해야한다면 2번에서 배운 설정 정보를 직접 추가해야 합니다.

 

 

정리.

  1. 애노테이션(@PostConstruct, @PreDestroy)을 사용하자.
  2. 외부 라이브러리의 경우 @BeaninitMethod, destroyMethod를 사용하자.

입니다.

 

 

이렇게 3가지 지원방식에 대해서 알아봤습니다.

포스트를 마치겠습니다.

 

 

 

 

 

출처: 인프런 - 스프링 핵심원리(기본편) by 김영한