Backend/Spring

빈 생명주기 콜백을 통한 스프링 빈 객체의 초기화/종료 작업

olsohee 2023. 12. 29. 18:15

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

 

객체의 초기화는 스프링 컨테이너에 해당 객체가 스프링 빈으로 생성되고 의존관계 주입이 완료된 후에 진행해야 하고, 객체의 종료는 해당 객체의 빈이 소멸되기 직전에 진행해야 한다.

  • 참고로 객체가 스프링 빈으로 생성된 후에 객체의 초기화 작업을 해야 하는 이유는 다음과 같다.
  • 생성자는 필수 정보를 파라미터로 받고 메모리를 할당해서 객체를 생성하는 책임을 가진다. 반면 초기화는 이렇게 생성된 객체를 외부 커넥션과 연결하는 등 무거운 동작을 수행한다. 따라서 생성자 안에서 무거운 초기화 작업까지 함께 하는 것보다는 객체를 생성하는 부분과 초기화하는 부분을 분리하는 것이 좋다.

그렇다면 개발자는 스프링 빈 객체가 생성됐는지 어떻게 알 수 있을까? 스프링은 빈 객체가 생성되고 의존관계 주입이 완료되면 초기화 콜백 메소드를 통해 그 시점을 알려주는 다양한 기능들을 제공한다. 또한 빈 객체가 소멸되기 직전에 소멸 콜백을 준다. 즉, 스프링 빈의 라이프사이클은 다음과 같다.

  • 스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입(생성자 주입은 빈 생성과 의존관계 주입이 동시에 일어난다.) -> 초기화 콜백 -> 스프링 빈 사용 -> 소멸 전 콜백 -> 스프링 빈 소멸

이러한 빈 생명주기 콜백을 제공하는 방법은 다음 3가지가 있다. 이 중 마지막 방법인 @PostConstruct와 @PreDestroy을 주로 사용한다.

  • InitializingBean, DisposableBean 인터페이스
  • 수동 빈 등록 시 초기화 메소드와 종료 메소드 지정
  • @PostConstruct, @PreDestroy 애노테이션

InitializingBean, DisposableBean 인터페이스

InitializingBean은 afterPropertiesSet() 메소드를, DisposableBean은 destroy() 메소드를 제공한다. 따라서 빈으로 등록할 클래스가 두 인터페이스를 구현하면, 해당 객체가 빈으로 생성되고 의존관계 주입이 완료된 후에 afterPropertiesSet() 메소드가 호출되고, 빈이 소멸되기 직전에 destroy() 메소드가 호출된다.

public class NetworkClient implements InitializingBean, DisposableBean {

	@Override
	public void afterPropertiesSet() throws Exception {
		// 초기화 작업
	}
    
	@Override
	public void destroy() throws Exception {
		// 종료 작업
	}
}

그러나 이 방법은 다음과 같은 특징으로 잘 사용되지 않는다.

  • InitializingBean와 DisposableBean은 스프링에서 제공하는 인터페이스이기 때문에 빈으로 등록할 클래스가 스프링에 의존하게 된다.
  • 초기화와 소멸 메소드의 이름을 변경할 수 없다. 따라서 내가 고칠 수 없는 외부 라이브러리에 적용할 수 없다.

빈 등록 시 초기화 메소드와 종료 메소드 지정

다음과 같이 수동으로 빈을 등록할 때 @Bean 어노테이션의 속성으로 초기화 메소드와 종료 메소드의 이름을 지정해주면 된다. 그러면 해당 빈이 등록되고 의존관계 주입이 완료되면 지정한 초기화 메소드가 호출되고, 빈이 소멸되기 직전에 지정한 종료 메소드가 호출된다.

@Configuration
public class LifeCycleConfig {

	@Bean(initMethod = "init", destroyMethod = "close")
	public NetworkClient networkClient() {
		return new NetworkClient();
	} 
}

빈 등록 시 초기화 메소드와 종료 메소드를 지정해주는 방법은 다음과 같은 특징을 갖는다.

  • 메소드 이름을 자유롭게 정의할 수 있다.
  • 빈으로 등록되는 클래스가 스프링 코드에 의존하지 않는다.
  • 내가 고칠 수 없는 외부 라이브러리에도 초기화, 종료 메소드를 지정해 줄 수 있다.
  • 종료 메소드 추론 기능을 갖는다.
    • @Bean의 destroyMethod 속성은 추론 기능을 갖는다.
    • 대부분의 라이브러리는 close, shutdown이라는 이름의 종료 메소드를 사용한다.
    • 따라서 @Bean의 destroyMethod는 기본값이 (inferred)로 되어 있어서, 별도로 종료 메소드를 지정해주지 않으면  close, shutdown이라는 이름의 메소드를 자동으로 호출해준다. 
    • 추론 기능을 사용하기 싫으면 destroyMethod=""처럼 빈 공백을 지정하면 된다.

@PostConstruct, @PreDestroy

초기화 메소드로 지정할 메소드에 @PostConstruct 어노테이션을, 종료 메소드로 지정할 메소드에 @PreDestroy 어노테이션을 붙여주면 된다.

public class NetworkClient {

	@PostConstruct
	public void afterPropertiesSet() throws Exception {
		// 초기화 작업
	}
    
	@PreDestroy
	public void destroy() throws Exception {
		// 종료 작업
	}
}
  • 스프링에서 가장 권장하는 방법으로, 어노테이션만 붙이면 되므로 매우 편리하다.
  • @PostConstruct, @PreDestroy 어노테이션은 자바 표준이다. 따라서 빈으로 등록할 클래스가 스프링에 의존하지 않는다.
  • 유일한 단점은, 내가 고칠 수 없는 외부 라이브러리에는 적용할 수 없다는 것이다. 따라서 @PostConstruct, @PreDestroy 어노테이션을 기본으로 사용하고 코드를 고칠 수 없는 외부 라이브러리를 초기화/종료해야 하면 @Bean의 initMethod, destroyMethod를 사용하자.

Reference

  • 인프런, 스프링 핵심 원리 - 기본편, 김영한

'Backend > Spring' 카테고리의 다른 글

Web Server, WAS, Servlet  (1) 2024.01.02
빈 스코프  (1) 2023.12.30
스프링 빈 등록과 조회  (0) 2023.12.29
컴포넌트 스캔과 자동 의존관계 주입  (0) 2023.12.29
스프링 컨테이너와 스프링 빈  (1) 2023.12.28