[Spring-기본] 빈 생명주기 콜백

DANI·2023년 11월 27일

Spring[김영한T]

목록 보기
28/31
post-thumbnail

📕 빈 생명주기 콜백


데이터베이스 커넥션 풀이나 네트워크 소켓처럼 애플리케이션 시작 시점에 미리 연결을 해두고, 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면, 객체의 초기화와 종료 작업이 필요하다.




❗ 외부 네트워크에 미리 연결하는 객체를 생성한다고 가정해보자!(메세지만 출력)

💾 NetworkClient (외부에 미리 연결하는 객체)

package hello.core.lifecycle;

public class NetworkClient {

    private String url;

    public NetworkClient() {
        System.out.println("생성자 호출, url = " + url);
        // 생성과 동시에 연결
        connect();
        // 초기화 메세지 출력
        call("초기화 연결 메세지");
    }

    // 생성과 동시에 호출 됨
    // 서비스 시작 시 호출
    public void connect() {
        System.out.println("connect : " + url);
    }

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

    // 생성과 동시에 초기화 메세지 출력
    public void call(String msg){
        System.out.println("call = " + url + " msg = " + msg);
    }

    // 서비스 종료 시 호출
    public void disconnect(){
        System.out.println("close = " + url);
    }

}

🔵 Test 코드

package hello.core.lifecycle;

import org.junit.jupiter.api.DisplayName;
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
    @DisplayName("빈 라이프사이클")
    public void lifeCycleTest(){
      ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
        NetworkClient bean = ac.getBean(NetworkClient.class);
        ac.close();

    }

    @Configuration
    static class LifeCycleConfig{

        @Bean
        public NetworkClient networkClient(){
            // 인스턴스 생성
            NetworkClient networkClient = new NetworkClient();
            // 수정자 주입으로 url 삽입
            networkClient.setUrl("http://hello-spring.dev");
           // networkClient.call("안녕하세요");
            return networkClient;
        }
    }
}

networkClient@Bean으로 등록되면서 생성자 호출이 완료되고 그 후에 수정자 주입으로 url이 삽입된다. 따라서 null 값이 나오고 connect() 또한 null이 된다.




@Configuration
  static class LifeCycleConfig{

      @Bean
      public NetworkClient networkClient(){
         
         NetworkClient networkClient = new NetworkClient();
          
         networkClient.setUrl("http://hello-spring.dev");
         networkClient.call("안녕하세요");
          return networkClient;
      }
  }

✨위와 같이 의존관계 주입이 완료된 후 객체를 호출하게 되면 아래와 같이 정상적으로 호출된다.





✨ 스프링 빈은 객체를 생성 한 후에 의존관계가 주입된다. ✨

  • 초기화 콜백 : 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출

  • 소멸전 콜백 : 빈이 소멸되기 직전에 호출



💡 객체의 생성과 초기화를 분리하는 것이 좋다


생성자는 필수 정보(파라미터)를 받고, 메모리를 할당해서 객체를 생성해주는 책임을 갖는다. 초기화는 생성된 객체를 바탕으로 외부 커넥션을 연결하는 등 무거운 동작을 한다. 따라서 생성자 안에서 무거운 초기화 작업을 하는 것 보다 생성하는 부분과 초기화 하는 부분을 나누는 것이 유지보수 관점에서 좋다.





🔐 스프링의 빈 생명주기 콜백 지원 방법

✅ 1. 인터페이스(IntializingBean, DisposableBean)
✅ 2. 설정 정보에 초기화 메서드, 종료 메서드 지정
✅ 3. @PostConstruct,@PreDestory 애너테이션 지원



🔑 1. 인터페이스(IntializingBean, DisposableBean)


💾 NetworkClient 수정

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

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

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



    public void call(String msg){
        System.out.println("call = " + url + " msg = " + msg);
    }

    // 서비스 종료 시 호출
    public void disconnect(){
        System.out.println("close = " + url);
    }

    // DisposableBean 오버라이딩
    // 소멸
    @Override
    public void destroy() throws Exception {
        disconnect();
    }

    // InitializingBean 오버라이딩
    // 초기화
    @Override
    public void afterPropertiesSet() throws Exception {
        connect();
        call("초기화 연결 메세지");
    }
}

🔵 테스트 결과


🔑 인터페이스의 단점

  • 스프링 전용 인터페이스라서 외부 라이브러리에 적용할 수 없다.
  • 초기화, 소멸 메서드의 이름을 변경할 수 없다.

😅 잘 사용하지 않는 방법!




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


💾 NetworkClient 수정

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

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

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



    public void call(String msg){
        System.out.println("call = " + url + " msg = " + msg);
    }

    // 서비스 종료 시 호출
    public void disconnect(){
        System.out.println("close = " + url);
    }

 
    // 소멸
    public void close() {
        disconnect();
    }

    
    // 연결 후
    public void init(){
        connect();
        call("초기화 연결 메세지");
    }
}

🔵 Test 코드 수정

package hello.core.lifecycle;

import org.junit.jupiter.api.DisplayName;
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
    @DisplayName("빈 라이프사이클")
    public void lifeCycleTest(){
      ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
        NetworkClient bean = 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://hello-spring.dev");
            return networkClient;
        }
    }
}

🔵 테스트 결과


🔑 설정 정보 특징

  • 메서드 이름 변경 가능
  • 설정 정보를 사용하기 때문에 코드를 고칠 수 없는 외부라이브러리

    @BeandestroyMethod 속성에는 기본 값이 (inferred)(추론)으로 등록되어 있어서 clse, shutdown이라는 이름의 메서드를 자동으로 호출해준다. 따라서 종료 메서드는 따로 적어주지 않아도 잘 동작하게된다.




🔑 3. @PostConstruct,@PreDestory 애너테이션 지원


💾 NetworkClient 수정

package hello.core.lifecycle;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
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);
    }

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

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



    public void call(String msg){
        System.out.println("call = " + url + " msg = " + msg);
    }

    // 서비스 종료 시 호출
    public void disconnect(){
        System.out.println("close = " + url);
    }


    // 소멸
    @PreDestroy
    public void close() {
        disconnect();
    }

    // 초기화
    @PostConstruct
    public void init(){
        connect();
        call("초기화 연결 메세지");
    }
}

🔵 Test 코드 수정

package hello.core.lifecycle;

import org.junit.jupiter.api.DisplayName;
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
    @DisplayName("빈 라이프사이클")
    public void lifeCycleTest(){
      ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
        NetworkClient bean = ac.getBean(NetworkClient.class);
        ac.close();

    }

    @Configuration
    static class LifeCycleConfig{

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

🔵 테스트 결과


🔑@PostConstruct @PreDestory 애너테이션 특징

  • 패키지가 import jakarta.annotation.PostConstruct; 이다. 자바 표준이므로 스프링이 아닌 다른 컨테이너에서도 동작한다.
  • 컴포넌트 스캔과 잘 어울리도 편리하다
  • 외부 라이브러리에 적용하지 못한다.

✨ 스프링에서 권장하는 방법 ✨ 외부 라이브러리를 초기화해야 할 경우 @Bean의 initMethod, destroyMethod를 이용하자!

0개의 댓글