Spring

[Spring] 빈 생명주기 콜백(Bean LifeCycle Callback)

cloud-grace 2024. 6. 8. 22:36

빈 생명주기 콜백(Bean LifeCycle Callback)이 필요한 이유?

스프링 컨테이너가 객체를 관리한다는 것은 객체가 생성하고 소멸하는 생명주기 LifeCycle를 관리한다는 것이다.
 
여기서 콜백 함수란, 특정 이벤트나 조건이 발생했을 때 호출되는 함수이다.
 
Spring 프로젝트 시작 시, DB 연결, 소켓 연결 등 시간이 걸려 미리 연결을 하고, 애플리케이션 종료 시점에 연결을 종료해야 하는 경우가 있다. 이 경우에는 객체를 초기화하고 종료하는 작업을 해줘야 한다.
예 : Connection Pool의 Connect, Disconnect
 
스프링 빈도 초기화와 종료 작업이 진행된다. 객체 생성, 의존 관계 주입의 생명주기를 가지고 있다.
결국, Spring Bean은 객체 생성과 의존 관계 주입이 완료되어야 필요한 데이터를 사용할 준비가 된다.
 
내가 직접 어떠한 빈의 초기화 작업을 해주고 싶다면, 의존관계가 모두 주입된 이후 초기화 메서드를 호출해야 한다.
개발자가 의존 관계 주입이 완료되는 것을 알려면, 스프링이 Bean의 의존 관계 주입 이후 콜백 메서드를 활용하여 초기화 시점을 알려주는 기능을 갖추었기 때문에 이를 활용하면 된다. 또한, 소멸 역시 소멸 직전에 소멸 콜백 메서드를 활용하여 컨테이너가 종료되기 전, 로직을 수행할 수 있다.
 

DI 의존 관계 주입

의존 관계 주입 관련 내용 포스팅

[Spring] IoC(Inversion of Control 제어의 역전), DI(Dependency Injection 의존성 주입), 의존성 주입 3가지 방법

IoC(Inversion of Control, 제어의 역전)객체 생성 및 의존성 주입 등의 제어를 개발자가 아닌 프레임워크가 담당하도록 하는 설계 원칙이다.사용할 객체를 직접 생성하지 않고, 객체 생명주기 관리를

cloud-grace.tistory.com

 자세한 내용은 포스팅에 담겨 있다.
 
해당 포스팅에 따라, 의존 관계를 주입하는 데에는 3가지 방법이 있다고 나와있다.
의존 관계 주입 이전에는 객체 생성이 이루어지는 데, 객체 생성과 의존 관계 주입이 3가지 방법마다 진행되는 것이 조금 다르다.

  • 수정자 주입 & 필드 주입 : 객체 생성 → 의존 관계 주입 (생명주기 나뉘어짐)
  • 생성자 주입 : 객체 생성 & 의존 관계 주입 (동시에 수행됨)

 

생성자 주입이 동시에 수행되는 이유

  • 아래는 CafeService 객체에 AmericanoRecipe 객체와 의존 관계가 존재한다.
@Service
public class CafeService {
    private final AmericanoRecipe americanoRecipe;
    
    @Autowired
    public CafeService(AmericanoRecipe americanoRecipe) {
        this.americanoRecipe = new AmericanoRecipe();
    }
}
  • 만약, 의존 관계가 존재하지 않는다면, 아래처럼 객체 생성을 할 수 없다.
  • 즉, 객체 생성과 의존 관계 주입이 동시에 일어난다.
  • 이를 통해, null 주입 이외의 NullPointerException을 방지할 수 있다.
  • 의존 관계 주입을 하지 않았다면 객체 생성을 할 수 없으므로 의존 관계에 대한 내용이 잘 보이고, 컴파일 단계에서 에러를 잡을 수 있다.
public class Main {
    public static void main(String[] args) {
    
    CafeService cafeService = new CafeService(new AmericanoRecipe());
    
    // CafeService cafeService = new CafeService(); Error
    }
}

 
 

스프링 빈 이벤트 라이프 사이클

스프링 컨테이너 생성 → 스프링 빈 생성 → 의존 관계 주입 → 초기화 콜백 메서드 호출 → 사용 → 소멸 전 콜백 메서드 호출 → 스프링 종료
  • 초기화 콜백 : 빈 생성 및 의존 관계 주입 완료 후 호출
  • 소멸 전 콜백 : 빈 소멸 직전 호출

 

객체 생성 & 초기화 분리

생성자는 parameter를 받으며, 메모리 할당 및 객체 생성하는 책임이 있다.
초기화는 이와 같이 생성된 값을 통해 외부 연결 등의 여러 무거운 일을 수행한다. 따라서 생성과 초기화를 분리하지 않으면 SRP(단일 책임 원칙)에 어긋나기도 하여 객체 지향 및 유지 보수성을 높이기 위해서 이 둘을 분리해야 한다.
 

스프링 빈 생명주기 콜백 관리 방법 3가지

1. 인터페이스 InitializingBean, DisposableBean

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

public class Client implements InitializingBean, DisposableBean {

    @Override
    public void afterPropertiesSet() throws Exception {
    
    }
    
    @Override
    public void destroy() throws Exception {
    
    }
}
  • InitializingBean : afterPropertiesSet() 메서드로 초기화 지원, 의존 관계 주입 이후 초기화 진행
  • DisposableBean : destroy() 메서드로 소멸 지원, Bean 종료 직전 마무리 작업(ex. close() 등의 자원 해제)
  • 이 2가지 인터페이스는 Spring 전용이며, 코드들이 인터페이스에 의존한다.
  • 메서드명 변경도 어렵고, 코드를 직접 변경하여 외부 라이브러리에 적용을 하지 못한다.

 

2. 빈 등록 초기화, 소멸 메서드

  • 스프링 빈 등록 시 사용하는 @Bean 어노테이션과 해당 속성으로 초기화 & 소멸 메서드 이름을 지정할 수 있다.
  • 메서드 이름을 자유롭게 지정 가능하다.
  • 스프링 코드에 의존하지 않는다.
  • 외부 라이브러리에도 적용 가능하다.
  • 하지만, 직접 Bean 등록 시 직접 지정해야 해서 번거롭다.
@Bean(initMethod = "초기화 메서드 이름", destroyMethod="소멸 메서드 이름")
@Configuration
public class LifeCycleAppConfig {
    @Bean(initMethod = "init", destroyMethod = "close")
    public Client client() {
        Client client = new Client();
        return client;
    }
}
public class Client{
    public void init() { // 초기화 콜백(의존 관계 주입 이후 호출)
    
    }

    public void close() { // 소멸 전 콜백(연결 종료 및 메모리 해제 수행)

    }
}

 

종료 메서드 추론

Bean의 소멸 메서드는 주로 close, shutdown 등의 이름이 많아, 이를 소멸 메서드로 추론(inferred)하고 자동으로 호출해준다. 따로 소멸 메서드를 부여하지 않아도 된다. 추론 기능을 사용하지 않으려면 destroyMethod = " " 이렇게 공백으로 지정하면 된다.
 

3. 어노테이션 @PostConstruct, @PreDestroy

  • 가장 권장하는 방법이며, 초기화 메서드에 @PostConstruct, 소멸 메서드에 @PreDestroy 어노테이션을 각각 지정하면 되기 때문에 매우 편하다.
  • 이는 스프링 종속이 아닌 자바 표준이다.
  • 외부 라이브러리 적용은 불가능하다.
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class Client{
    
    @PostConstruct
    public void init() { // 초기화 콜백(의존 관계 주입 이후 호출)
    
    }
    
    @PreDestroy
    public void close() { // 소멸 전 콜백(연결 종료 및 메모리 해제 수행)

    }
}

 
 
 

참고 자료
내용 참고 : 인프런 김영한 님의 강의 "스프링 핵심 원리 - 기본편"
https://daegwonkim.tistory.com/284
https://catsbi.oopy.io/3a9e3492-f511-483d-bc65-183bb0c166b3
https://dev-coco.tistory.com/170