스프링 빈 생명주기 콜백

박찬우·2023년 12월 20일
0

스프링

목록 보기
22/88

스프링 빈 생명주기 콜백

  • 스프링 빈 라이프 사이클 : 객체생성 -> 의존관계 주입
  • 객체가 생성되고 의존관계 주입이 완료된 시점부터 필요한 데이터를 사용할 준비가 완료된다
  • 스프링은 초기화 콜백을 통해 초기화 시점을 알려주는 기능과 스프링 컨테이너가 종료되는 시점을 알려주느 소멸전 콜백을 준다
  • 초기화 콜백: 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출
  • 소멸전 콜백: 빈이 소멸되기 직전에 호출
  • 스프링 빈의 이벤트 라이프 사이클 : 스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸전 콜백 -> 스프링 종료
  • 초기화 콜백 시점이 다른 경우 : 생성자 주입(생성과정에서 초기화를 해주기 때문)
  • 생성자 주입을 사용하면 따로 콜백을 사용하지 않아도 되지만 보통 데이터베이스, 네트워크 연결 등 무거운 작업을 하는 경우 생성과 초기화 부분을 나누는 것이 좋기 때문에 생성자 주입을 사용하지 않는다
  • 소멸전 콜백 시점이 다른 경우 : 생명주기가 짧은 빈들도 있는데 이 빈들은 컨테이너와 무관하게 해당 빈이 종료되기 직전에 소멸전 콜백이 일어난다
  • 싱글톤의 경우 스프링 컨테이너가 종료되는 시점에 같이 종료가 되기 때문에 스프링 컨테이너 종료 전에 소멸전 콜백이 일어난다

빈 생명주기 콜백 종류 3 가지

  • 인터페이스(InitializingBean, DisposableBean)
  • 설정 정보에 초기화 메서드, 종료 메서드 지정
  • @PostConstruct, @PreDestroy 애노테이션 지원

인터페이스(InitializingBean, DisposableBean)

  • 인터페이스를 상속받아서 진행함
  • InitializingBeanafterPropertiesSet() 메서드로 초기화를 지원한다. DisposableBeandestroy() 메서드로 소멸을 지원한다.
  • 그러나 옛날 방식으로 현재는 잘 사용하지 않음
  • 예)
    NetworkCliecnt
package hello.core.lifecycle;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

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+"messag = "+message);
    }

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

    // 4. 초기화 콜백
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("생성자 호출, url = " + url);
        connect();
        call("초기화 연결 메시지");
    }

    
    // 5. 소멸 전 콜백
    @Override
    public void destroy() throws Exception {
        disconnect();
    }
}

Test

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() {
        // 1. 스프링 컨테이너 생성
        // 2. 빈 생성
        ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);

        // 3. 의존 관계 주입
        NetworkClient networkClient = ac.getBean(NetworkClient.class);

        // 6. 종료
        ac.close();
    }

    @Configuration
    static class LifeCycleConfig {
        @Bean
        public NetworkClient networkClient() {
            NetworkClient networkClient = new NetworkClient();
            networkClient.setUrl("http://hello-spring.dev");
            return networkClient;
        }
    }
}

빈 등록 초기화, 소멸 메서드 지정

  • 설정 정보에 @Bean(initMethod = "init", destroyMethod = "close") 처럼 초기화, 소멸 메서드를 지 정할 수 있다
  • 메서드 이름을 자유롭게 줄 수 있다.
  • 스프링 빈이 스프링 코드에 의존하지 않는다.
  • 코드가 아니라 설정 정보를 사용하기 때문에 코드를 고칠 수 없는 외부 라이브러리에도 초기화, 종료 메서드를 적 용할 수 있다
  • 종료 메서드 추론
    • @Bean의 destroyMethod` 속성에는 아주 특별한 기능이 있다.
    • 라이브러리는 대부분 close , shutdown 이라는 이름의 종료 메서드를 사용한다.
    • @Bean의 destroyMethod 는 기본값이 (inferred) (추론)으로 등록되어 있다. 이 추론 기능은 close , shutdown 라는 이름의 메서드를 자동으로 호출해준다. 이름 그대로 종료 메서드를 추 론해서 호출해준다.
    • 따라서 직접 스프링 빈으로 등록하면 종료 메서드는 따로 적어주지 않아도 잘 동작한다.
    • 추론 기능을 사용하기 싫으면 destroyMethod="" 처럼 빈 공백을 지정하면 된다.
package hello.core.lifecycle;

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+"messag = "+message);
    }

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

    // 4. 초기화 콜백
    public void init() throws Exception {
        System.out.println("생성자 호출, url = " + url);
        connect();
        call("초기화 연결 메시지");
    }


    // 5. 소멸 전 콜백
    public void close() throws Exception {
        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() {
        // 1. 스프링 컨테이너 생성
        // 2. 빈 생성
        ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);

        // 3. 의존 관계 주입
        NetworkClient networkClient = ac.getBean(NetworkClient.class);

        // 6. 종료
        ac.close();
    }

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

@PostConstruct, @PreDestroy 애노테이션

  • 애노테이션 사용
  • 최신 스프링에서 가장 권장하는 방법이다.
  • 애노테이션 하나만 붙이면 되므로 매우 편리하다.
  • 패키지를 잘 보면 javax.annotation.PostConstruct 이다. 스프링에 종속적인 기술이 아니라 JSR-250 라는 자바 표준이다.
  • 따라서 스프링이 아닌 다른 컨테이너에서도 동작한다.
  • 컴포넌트 스캔과 잘 어울린다.
  • 유일한 단점은 외부 라이브러리에는 적용하지 못한다는 것이다.
  • 외부 라이브러리를 초기화, 종료 해야 하면 @BeaninitMethod , destroyMethod 사용
package hello.core.lifecycle;

import jakarta.annotation.PostConstruct;
import jakarta.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+"messag = "+message);
    }

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

    // 4. 초기화 콜백
    @PostConstruct
    public void init() throws Exception {
        System.out.println("생성자 호출, url = " + url);
        connect();
        call("초기화 연결 메시지");
    }


    // 5. 소멸 전 콜백
    @PreDestroy
    public void close() throws Exception {
        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() {
        // 1. 스프링 컨테이너 생성
        // 2. 빈 생성
        ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);

        // 3. 의존 관계 주입
        NetworkClient networkClient = ac.getBean(NetworkClient.class);

        // 6. 종료
        ac.close();
    }

    @Configuration
    static class LifeCycleConfig {
        @Bean
        public NetworkClient networkClient() {
            NetworkClient networkClient = new NetworkClient();
            networkClient.setUrl("http://hello-spring.dev");
            return networkClient;
        }
    }
}
profile
진짜 개발자가 되어보자

0개의 댓글