스프링은 IoC 제어의 역전
으로 인해 프로그래머가 아닌 스프링 IoC 컨테이너가 빈(객체)을 관리하게 됩니다. 따라서 빈의 생성부터 등록, 소멸까지의 과정을 스프링 컨테이너가 관리하기 때문에 프로그래머는 프로그램의 코드(logic)에만 집중할 수 있게 됩니다.
빈의 생성부터 소멸까지의 과정을 스프링 빈 라이프사이클(스프링 빈 생명주기)
라고 하는데요. 이 과정에서 콜백 함수를 통해 특정 시점에 특정 로직을 실행 하도록 만들 수 있습니다.
스프링 빈 라이프사이클(싱글톤 빈 기준)
은 다음과 같은 순서로 진행됩니다.
웹 관련 빈
들은 웹 요청, 세션의 라이프사이클을 따르기 때문에 스프링 컨테이너가 종료되기 전에 소멸이 발생합니다. 이런 빈 들의 경우 소멸 콜백은 스프링 컨테이너 종료 시점이 아니라 해당 빈이 종료되기 직전 시점에 호출됨에 유의해주세요.
스프링에서는 3 가지 방식으로 빈 생명주기 콜백들을 지원하고 있습니다. 지금부터 하나하나씩 알아보도록 하겠습니다.
첫 번째 방법은 @PostConstruct, @PreDestroy
어노테이션을 사용하는 것 입니다.
@PostConstruct, @PreDestroy
는 특정 메소드에 붙여주기만 하면 해당 메소드가 각각 초기화, 소멸 콜백 메소드가 되어 초기화/소멸 과정에서 호출이 됩니다.
package com.hello.myboot;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
public class LifecycleCallbacksClient {
public LifecycleCallbacksClient() {
System.out.println("LifecycleCallbacksClient 생성자 호출");
}
@PostConstruct //초기화 콜백 메소드
public void init() {
System.out.println("LifecycleCallbacksClient 초기화 콜백 호출");
}
@PreDestroy //소멸 콜백 메소드
public void destroy() {
System.out.println("LifecycleCallbacksClient 소멸 콜백 호출");
}
}
package com.hello.myboot;
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 LifecycleCallbacksTest {
@Test
public void lifecycleCallbacksTest() {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class);
LifecycleCallbacksClient lifecycleCallbacksClient = context.getBean(LifecycleCallbacksClient.class);
context.close();
}
@Configuration
static class TestConfig {
@Bean
public LifecycleCallbacksClient lifecycleCallbacksClient() {
return new LifecycleCallbacksClient();
}
}
}
빈을 등록(생성자)하고 초기화, 소멸 과정에서 각 메소드 내용이 제대로 호출됨을 볼 수 있습니다.
jakarta.annotation
패키지에 등록된 어노테이션입니다. 즉, spring.
으로 시작하는 스프링 프레임워크의 기능이 아닌 자바 표준 기술입니다. (= 스프링 외에서도 사용할 수 있다.)
어노테이션만 붙이면 되서 편리하다.
외부 라이브러리 빈에는 적용하기 어렵다.
스프링에서 권장하고 있는 방식이다.
스프링 공식 문서에도 이 표준을 다룬 페이지가 따로 있는 스프링 권장 사양입니다.
두 번째 방법은 @Bean
을 정의할 때 메타데이터(설정 정보)를 지정하는 방식입니다.
@Bean(initMethod = "초기화 콜백 함수명", destroyMethod = "소멸 콜백 함수명")
위 코드를 변경해서 빈 메타데이터 설정 방식으로 초기화/소멸 콜백 함수를 호출해보도록 만들겠습니다.
package com.hello.myboot;
public class LifecycleCallbacksClient {
public LifecycleCallbacksClient() {
System.out.println("LifecycleCallbacksClient 생성자 호출");
}
public void init() { //초기화 콜백 메소드
System.out.println("LifecycleCallbacksClient 초기화 콜백 호출");
}
public void destroy() { //소멸 콜백 메소드
System.out.println("LifecycleCallbacksClient 소멸 콜백 호출");
}
}
public class LifecycleCallbacksTest {
@Test
public void lifecycleCallbacksTest() {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class);
LifecycleCallbacksClient lifecycleCallbacksClient = context.getBean(LifecycleCallbacksClient.class);
context.close();
}
@Configuration
static class TestConfig {
@Bean(initMethod = "init", destroyMethod = "destroy")
public LifecycleCallbacksClient lifecycleCallbacksClient() {
return new LifecycleCallbacksClient();
}
}
}
역시 잘 호출 되었죠?
자유로운 메소드 이름 설정 가능(보통 암묵적으로 사용되는 init, close, destroy, shutdown 를 주로 사용)
코드가 아닌 메타데이터(설정 정보)이므로 외부 라이브러리에도 적용시킬 수 있다.
destroyMethod inferred
destroyMethod
는 inferred(추론)
동작을 수행합니다.
destroyMethod
의 추론은 빈에서 close, shutdown
이라는 이름을 가진 메소드를 찾아서 자동으로 호출하는 동작을 합니다.
따라서 종료 메소드의 이름을 close 또는 shutdown
으로 지정했을 때 destroyMethod
는 따로 적어주지 않아도 알아서 찾아서 종료 콜백을 실행합니다.
추론 기능을 사용하지 않는다면 destroyMethod
의 값을 ""
빈 문자열로 전달하면 됩니다.
세 번째 방법은 InitializingBean, DisposableBean 인터페이스
를 사용하는 방법입니다.
이 방식은 오래된 사양이기에 현재는 많이 사용되는 방식은 아니므로 이런게 있다 정도로만 보겠습니다.
초기화는 InitializingBean
, 소멸은 DisposableBean
인터페이스의 메소드들을 오버라이딩해서 사용합니다. 따라서 콜백 메소드의 이름을 변경할 수 없습니다.
public class LifecycleCallbacksClient implements InitializingBean, DisposableBean {
public LifecycleCallbacksClient() {
System.out.println("LifecycleCallbacksClient 생성자 호출");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("LifecycleCallbacksClient 초기화 콜백 호출");
}
@Override
public void destroy() throws Exception {
System.out.println("LifecycleCallbacksClient 소멸 콜백 호출");
}
}
public class LifecycleCallbacksTest {
@Test
public void lifecycleCallbacksTest() {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class);
LifecycleCallbacksClient lifecycleCallbacksClient = context.getBean(LifecycleCallbacksClient.class);
context.close();
}
@Configuration
static class TestConfig {
@Bean
public LifecycleCallbacksClient lifecycleCallbacksClient() {
return new LifecycleCallbacksClient();
}
}
}
오늘은 빈 생명주기 콜백에 대해 알아보았습니다.
권장사항인 @PostConstruct, @PreDestroy
를 우선해서 사용하되, 외부 라이브러리 등과 같이 고칠 수 없는 빈에 대해서는 빈 메타데이터
방법을 사용하시면 된다라고 할 수 있습니다.