-> 블로그 이전

[Spring - 기본] 빈 생명주기

2022. 5. 18. 21:17Language`/Spring

빈 객체 생명주기 (초기화 & 소멸)

DB 커넥션 풀이나 네트워크 Socket의 경우 "시작 시점에 미리 Connection을 설정해주거나 & 종료 시점에 연결을 안전하게 끊는 것"이 굉장히 중요하다

  • 객체의 초기화 & 종료 작업이 필요/중요

 

기본적으로 스프링 컨테이너가 스프링 설정 클래스로부터 여러 스프링 빈을 찾으면 다음 작업을 수행해준다

  1. 스프링 빈 생성
  2. DI
생성자 주입의 경우 스프링 빈이 생성될 때 DI도 같이 수행된다
setter나 field 주입의 경우 2단계가 나누어져서 진행된다

 

Spring은 DI까지 완료되면 스프링 빈에게 "너한테 DI까지 해줬으니까 초기화 과정 수행해라"라고 알려준다

>> 이 때 "초기화 콜백 메소드"를 통해서 알려준다

 

그리고 스프링 컨테이너를 종료하게 되면 여러 스프링 빈들에 대한 "소멸 콜백 메소드"를 호출해서 스프링 빈들도 소멸이 된다

  • 여기서 스프링 빈은 "Singleton"의 경우이다
  • Prototype Bean은 스프링 컨테이너가 종료된다고 해서 스프링 빈에 대한 소멸 콜백 메소드가 호출되지는 않는다
1. 스프링 컨테이너 생성
2. 스프링 빈 생성
3. DI
4. 초기화 콜백
...
'' 실행 ''
...
5. 스프링 컨테이너 종료
6. 소멸 콜백
7. 스프링 종료

 

초기화 & 소멸은 총 3가지 경우로 수행해줄 수 있다

1. InitializingBean & DisposableBean

public class BeanLifeCycleTest {

    @Configuration
    static class LifeCycleConfig{
        @Bean
        public NetworkClient networkClient(){
            NetworkClient networkClient = new NetworkClient();
            networkClient.setUrl("https://cs-ssupport.tistory.com/");
            return networkClient;
        }
    }

    @Test
    public void lifeCycleTest(){
        ConfigurableApplicationContext ac =
                new AnnotationConfigApplicationContext(LifeCycleConfig.class);

        NetworkClient client = ac.getBean(NetworkClient.class);
        ac.close();
    }
}
public class NetworkClient implements InitializingBean, DisposableBean {
    private String url;

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

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

    // 서비스 시작 시 connect() 호출
    public void connect(){
        System.out.println("초기화 1 >> connect : " + url);
    }

    public void call(){
        System.out.println("초기화 2 >> call : " + url);
    }

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

    @Override
    public void afterPropertiesSet() throws Exception {
        // DI 완료 후 호출되는 초기화 콜백 메소드
        connect();
        call();
    }

    @Override
    public void destroy() throws Exception {
        // 스프링 컨테이너를 종료하면 호출되는 소멸 콜백 메소드
        disconnect();
    }
}

InitializingBean은 "afterPropertiesSet()" & DisposableBean은 "destroy"를 Overriding해줌으로써 "초기화 & 소멸 콜백 메소드"를 정의한다

객체를 생성할 때는 url을 설정해주지 않기 때문에 당연히 url : null로 출력이 되는 것을 알 수 있다

 

이 방법의 단점은 다음과 같다

1. 스프링 전용 인터페이스이므로 해당 코드가 스프링 전용 인터페이스에 의존하게 된다
2. 초기화/소멸 메소드의 이름이 고정
3. 내가 코드를 고칠 수 없는 외부 라이브러리에 적용할 수 없다

 

초창기에 개발되었고 더 나은 방법이 있기 때문에 요즘에는 사용하지 않는다

 

2. Bean에 직접 설정

@Bean에는 초기화/소멸 메소드를 직접 만들어서 지정해줄 수 있다

 

public class BeanLifeCycleTest {

    @Configuration
    static class LifeCycleConfig{
        @Bean(initMethod = "init", destroyMethod = "destroy")
        public NetworkClient networkClient(){
            NetworkClient networkClient = new NetworkClient();
            networkClient.setUrl("https://cs-ssupport.tistory.com/");
            return networkClient;
        }
    }

    @Test
    public void lifeCycleTest(){
        ConfigurableApplicationContext ac =
                new AnnotationConfigApplicationContext(LifeCycleConfig.class);

        NetworkClient client = ac.getBean(NetworkClient.class);
        ac.close();
    }
}
public class NetworkClient{
    private String url;

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

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

    // 서비스 시작 시 connect() 호출
    public void connect(){
        System.out.println("초기화 1 >> connect : " + url);
    }

    public void call(){
        System.out.println("초기화 2 >> call : " + url);
    }

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

    public void init(){
        connect();
        call();
    }

    public void destroy(){
        disconnect();
    }
}

1. 초기화/소멸 메소드 이름을 개발자 임의로 작성할 수 있다
2. 스프링 빈이 스프링 코드에 의존하지 않는다
3. 코드가 아니라 설정 정보를 사용하기 때문에 코드를 고칠 수 없는 외부 라이브러리에도 초기화/소멸 메소드를 적용할 수 있다

 

그리고 @Bean에 destroyMethod를 보게되면 "inferred"라는 것이 보인다

이 말은 즉, 메소드에 대한 "추론"이 가능하다는 의미이다

 

이 추론 기능에 의해서 이름을 지정해주지 않아도 close / shutdown이라는 이름의 메소드를 자동으로 destoryMethod로 인식해서 호출해준다는 의미이다

 

추론 기능을 사용하기 싫으면 destroyMethod=""처럼 빈 공백을 지정하면 된다

 

3. Annotation : @PostConstructor & @PreDestroy

@PostConstructor는 초기화 메소드 위에다 붙여주면 되고, @PreDestroy는 소멸 메소드 위에다 붙여주면 된다

이 방법이 가장 간단하고 요즘에 가장 많이 쓰이는 방식이고 스프링에서는 이 방법을 권장하고 있다

public class BeanLifeCycleTest {

    @Configuration
    static class LifeCycleConfig{
        @Bean
        public NetworkClient networkClient(){
            NetworkClient networkClient = new NetworkClient();
            networkClient.setUrl("https://cs-ssupport.tistory.com/");
            return networkClient;
        }
    }

    @Test
    public void lifeCycleTest(){
        ConfigurableApplicationContext ac =
                new AnnotationConfigApplicationContext(LifeCycleConfig.class);

        NetworkClient client = ac.getBean(NetworkClient.class);
        ac.close();
    }
}
public class NetworkClient{
    private String url;

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

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

    // 서비스 시작 시 connect() 호출
    public void connect(){
        System.out.println("초기화 1 >> connect : " + url);
    }

    public void call(){
        System.out.println("초기화 2 >> call : " + url);
    }

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

    @PostConstruct
    public void init(){
        connect();
        call();
    }

    @PreDestroy
    public void destroy(){
        disconnect();
    }
}

 

이 애노테이션들은 javax라는 "자바 표준 기술"이다

따라서 스프링이 아닌 다른 컨테이너에서도 잘 동작한다

그리고 컴포넌트 스캔과 잘 어울린다

 

하지만 외부 라이브러리에는 적용을 하지 못한다. 따라서 고칠 수 없는 외부 라이브러리를 초기화, 종료해야 하면 @Bean에다가 설정해줘야 한다