JAVA/Spring_Core

[Spring] 스프링이 빈 생명주기 콜백을 해주는 방법

kth990303 2021. 4. 3. 10:20
반응형

빈 생명주기 콜백을 알아야 하는 이유와 객체의 초기화 및 스프링 종료에 대한 글은 아래 포스팅을 참고하면 된다.

kth990303.tistory.com/26

 

[Spring] 빈 생명주기 콜백과 객체의 초기화

4월이 되고 일병4호봉이 되었다. 그 기념 오늘은 스프링 공부를 하려 한다. (???) 사실 위 이유는 농담이고, 요즘 nodejs, 백준 알고리즘 공부로 인해 스프링부트 공부를 많이 진행하지 못했다. 스프

kth990303.tistory.com

오늘 포스팅은 위 포스팅의 연장선이라 보면 된다.

 

저번에도 말했듯이, 스프링이 빈 생명주기 콜백을 해주는 방법은 총 세가지가 있다고 하였다.

 

  1. 인터페이스(InitializingBean, DisposableBean)
  2. 초기화 메소드, 종료 메소드 지정
  3. @PostConstruct, @PreDestroy 어노테이션 지정

그 중에서 1번은 단점만 있고, 20년 전 방법이므로 따로 적지 않으려고 한다.

난 3번은 내 기억속에 가지고 갈 생각이고, 2번은 필요한 상황이 있을 때마다 이 포스팅을 내가 직접 보면서 기억을 상기시키려 하고, 1번은 정말 쓸일이 없을거라 판단해 아예 포스팅을 하지 않을 생각이다.

2번과 3번 방법을 기록해놓고 1번이 왜 쓰이지 않는지, 단점이 무엇인지에 대해 쓸 생각은 있다.

 


초기화 메소드, 종료 메소드 지정

 

아래는 웹서버와 연결하여 request로 주어진 url을 연동하는 코드이다.

package hello.core.lifecycle;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class NetworkClient {
    private String url;
    public NetworkClient(){
        System.out.println("생성자 호출 = "+url);
    }

    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 void init(){
        System.out.println("NetworkClient.init");
        connect();
        call("초기화 연결 메시지");
    }

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

 

package hello.core.lifecycle;

import org.junit.jupiter.api.Test;
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("https://hello-spring.dev");
            return networkClient;
        }
    }
}

 

아래와 같이 destroyMethod="close"를 제외해도 괜찮은데, 그 이유는 @Bean의 destroyMethod의 기본값이 (inferred)로 돼있기 때문이다. 따라서 close, shutdown의 종료 이름 메소드가 보인다 싶으면 그 메소드를 자동으로 호출해주기 때문에 아래와 같이 @Bean 어노테이션의 정보를 일부 생략해주어도 괜찮다.

package hello.core.lifecycle;

import org.junit.jupiter.api.Test;
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")
        public NetworkClient networkClient(){
            NetworkClient networkClient=new NetworkClient();
            networkClient.setUrl("https://hello-spring.dev");
            return networkClient;
        }
    }
}

 

이 방법의 특징은 아래와 같다.

  • 메소드 이름을 자유롭게 지정 가능
  • 스프링 빈이 스프링 코드에 의존하지 않는다. (@Bean과 같은 설정정보에 의존하지 코드 자체에 의존하진 않음)
  • 설정 정보를 사용하므로 외부 라이브러리리에도 초기화, 종료 메소드 적용 가능

마지막 세 번째 특징 때문에 객체 초기화, 종료 함수 지정해주는 방법이 쓰일 때가 있다고 한다.


@PostConstruct, @PreDestroy 어노테이션 지정

 

아래와 같이 객체 초기화 함수랑 종료함수에 @PostConstruct 어노테이션이랑 @PreDestroy 어노테이션을 붙여주기만 하면 된다.

 

최신 스프링에서 가장 권장하는 방법으로,

어노테이션만 붙이면 자동으로 객체 초기화, 종료 시점에 메소드를 호출해준다.

package hello.core.lifecycle;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class NetworkClient {
    private String url;
    public NetworkClient(){
        System.out.println("생성자 호출 = "+url);
    }

    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);
    }
    @PostConstruct
    public void init(){
        System.out.println("NetworkClient.init");
        connect();
        call("초기화 연결 메시지");
    }
    @PreDestroy
    public void close(){
        System.out.println("NetworkClient.close");
        disconnect();
    }
}
package hello.core.lifecycle;

import org.junit.jupiter.api.Test;
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
        public NetworkClient networkClient(){
            NetworkClient networkClient=new NetworkClient();
            networkClient.setUrl("https://hello-spring.dev");
            return networkClient;
        }
    }
}

 

@Bean 설정정보를 따로 적지 않아도 @PostConstruct, @PreDestroy에 의해 알아서 초기화 및 종료가 된 것을 확인할 수 있을 것이다.

이 방법의 특징은 아래와 같다.

  • javax 패키지 내장 어노테이션으로 스프링 종속 기술이 아닌, 자바 표준이다. 따라서 스프링이 아닌 다른 컨테이너에서도 작동한다.
  • 어노테이션으로 설정 정보를 지정해주는 컴포넌트 스캔과 잘 어울린다.
  • 외부 라이브러리 초기화, 종료 시엔 사용이 불가능하여 이때는 위에서 메소드 지정한 방법인 @Bean의 기능을 사용해야 한다.

 


인터페이스 방법의 단점

인터페이스 방법은 20년 된 방법이기도 하고, 2번 방법의 장점을 완전 반대로 한 특성을 가진다 보면 된다.

아래는 2번 방법의 장점이다.

  • 메소드 이름을 자유롭게 지정 가능
  • 스프링 빈이 스프링 코드에 의존하지 않는다. (@Bean과 같은 설정정보에 의존하지 코드 자체에 의존하진 않음)
  • 설정 정보를 사용하므로 외부 라이브러리리에도 초기화, 종료 메소드 적용 가능

아래는 1번 방법의 단점이다.

  • 인터페이스 오버라이딩 메소드이므로 이름 따로 지정 불가
  • 스프링 빈이 스프링 코드에 의존함.
  • 코드에 의존하므로 외부 라이브러리리에 초기화, 종료 메소드 적용 불가능

따라서 3번 방법을 주로 이용하다가, 외부 라이브러리에 초기화, 종료 메소드를 적용해야 할 상황이 오면 2번 방법을 사용하면 될 듯하다.


사실 아직은 객체의 초기화 및 종료 설정을 언제 해주어야 할 지 감이 잘 잡히지 않는다.

큰 프로젝트를 진행해보지 않아서 그럴 수 있겠다.

 

확실한건 지금 내가 필요성을 느끼지 못하고 있으므로 곧 까먹을 게 뻔하여 급하게 내 블로그에 포스팅해놓았다.

반응형