이전 글인 스프링 컨테이너와 스프링 빈과 스프링 컴포넌트 스캔에서 스프링 컨테이너가 스프링 빈 설정 정보를 참고하여 스프링 빈을 생성함을 확인했습니다. 이를 다시 한 번 살펴보겠습니다.
스프링 컨테이너가 생성되고 스프링 빈 설정 정보를 기반으로 스프링 빈을 등록합니다. 그리고 스프링 컨테이너는 대개 애플리케이션 종료 전 소멸됩니다. 컨테이너가 소멸되기 직전에 스프링 빈이 소멸됩니다.
스프링 빈은 스프링 컨테이너에 의해 생성됩니다. 이전 글인 스프링 컴포넌트 스캔과 스프링 의존관계 자동 주입에서 봤듯 스프링 빈이 필요로 하는 빈들이 주입되고 각 필드의 값이 초기화되고 사용한 후 소멸됩니다.
빈 생명 주기
인스턴스화 및 의존관계 주입
스프링 빈 설정 정보 확인 및 BeanDefinition 객체 생성
이전 글인 스프링 컨테이너와 스프링 빈과 스프링 컴포넌트 스캔에서 XML, 클래스, 애너테이션을 기반으로 스프링 빈을 생성함을 확인했습니다.
더 자세히 나아가면 BeanDefinitionReader를 통해 설정 정보를 추출하고 BeanDefinition 객체를 생성합니다.
이후 BeanFactoryPostProcessor가 스프링 빈을 생성하기 전 BeanDefintion 객체에 설정 정보를 추가, 수정, 삭제하는 과정을 거치게 됩니다.
스프링 빈 인스턴스 생성
해당 과정을 완료하면 BeanFactory(스프링 컨테이너)가 BeanDefinition 객체의 설정 정보를 참고해 각 스프링 빈 클래스의 생성자를 호출해 스프링 빈 인스턴스를 생성합니다.
의존 관계 주입
생성자에 필드에 값을 넣는 명령이 있으면 생성자의 파라미터를 통해 의존 관계가 주입됩니다. 이후 필드, 수정자, 메서드 주입이 실행됩니다. 이에 대한 내용은 이전 글인 스프링 의존관계 자동 주입에서 확인할 수 있습니다.
스프링 빈과 컨테이너 요소 설정
스프링 빈이 BeanNameAware, BeanClassLoaderAware, ApplicationContextAware 인터페이스를 구현하고 있다면 이 인터페이스가 가진 메서드를 통해 로그 확인에 도움을 받을 수 있습니다.
BeanNameAware
BeanNameAware 인터페이스에 정의된 setBeanName(String beanName)
메서드를 통해 스프링 빈 이름을 얻을 수 있습니다.
스프링 컨테이너가 위 인스턴스화 및 의존관계 주입 과정을 마치면 스프링 컨테이너에 등록된 현재 스프링 빈의 이름을 인자로 하여 setBeanName(String beanName)
메서드를 호출합니다.
이에 대한 자세한 정보는 다른 블로그의 좋은 글들을 봐주시기 바랍니다. (#1, ...)
BeanClassLoaderAware
BeanClassLoaderAware 인터페이스에 정의된 setBeanClassLoader(ClassLoader classLoader)
메서드를 통해 스프링 컨테이너가 스프링 빈 생성 시 사용한 클래스 로더를 얻을 수 있습니다.
스프링 컨테이너가 위 인스턴스화 및 의존관계 주입 과정을 마치면 현재 스프링 빈을 생성한 클래스 로더를 인자로 하여 setBeanClassLoaderAware(ClassLoader classLoader)
메서드를 호출합니다.
이에 대한 자세한 정보는 다른 블로그의 좋은 글들을 봐주시기 바랍니다. (#1, ...)
ApplicationContextAware
ApplicationContextAware 인터페이스에 정의된 setApplicationContextAware(ApplicationContext applicationContext)
메서드를 통해 현재 스프링 빈이 생성된 스프링 컨테이너를 얻을 수 있습니다.
스프링 컨테이너가 위 인스턴스화 및 의존관계 주입 과정을 마치면 현재 스프링 빈을 생성한 스프링 컨테이너를 인자로 하여 setApplicationContextAware(ApplicationContext applicationContext)
메서드를 호출합니다.
이에 대한 자세한 정보는 다른 블로그의 좋은 글들을 봐주시기 바랍니다. (#1, ...)
빈 초기화
BeanPostProcess가 스프링 빈에 설정된 초기화 메서드를 실행해 스프링 빈을 초기화합니다.
초기화 작업에는 객체의 필드에 값을 저장하거나 데이터베이스와 빈을 연결하고 설정하는 작업 등이 해당한다고 볼 수 있겠습니다.
빈 소멸
BeanPostProcess가 스프링 빈에 설정된 소멸 전 작업 메서드를 실행해 스프링 빈 소멸 전 작업을 처리합니다. 이후 스프링 빈이었던 객체 인스턴스가 JVM의 가비지 컬렉션에 의해 소멸됩니다.
다만 프로토타입 빈은 스프링 컨테이너가 빈 초기화까지의 단계만 실행하고 관리하지 않으므로 빈 관리와 소멸은 개발자가 직접 처리해야 합니다.
소멸 전 작업에는 데이터베이스와 빈을 해제하는 작업 등이 해당한다고 볼 수 있겠습니다.
빈 생명 주기 콜백
InitializingBean과 DisposableBean 인터페이스
스프링 빈이 InitializingBean과 DisposableBean 인터페이스를 구현하도록 하여 초기화 메서드와 소멸 전 작업 메서드를 실행하도록 할 수 있습니다.
InitializingBean 인터페이스를 구현하면 void afterPropertiesSet()
메서드를 작성해야 합니다. DisposableBean 인터페이스를 구현하면 void destroy()
메서드를 작성해야 합니다.
인터페이스를 구현하는 방식은 스프링이 제공하는 인터페이스에 의존하게 되고 메서드 이름을 바꿀 수 없다는 것과 코드를 개발자가 직접 바꿀 수 없는 외부 라이브러리에 적용할 수 없다는 단점이 존재합니다.
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class LifecycleBeanWithInterface implements LifecycleBean, InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
// 초기화 콜백
}
@Override
public void destroy() throws Exception {
// 소멸 전 콜백
}
}
설정 정보
설정 정보에 해당 스프링 빈이 생성되고 소멸될 때 실행될 해당 스프링 빈이 가진 내부 메서드를 지정할 수 있습니다.
설정 정보에 지정하는 방식은 조금 번거롭더라도 메서드 이름을 자유롭게 할 수 있고 스프링 빈이 스프링이 제공하는 인터페이스에 의존하지 않게 되며 설정 정보를 사용하므로 외부 라이브러리에 적용할 수 있게 됩니다.
public class LifecycleBeanWithConfiguration implements LifecycleBean {
public void init() throws Exception {
// 초기화 콜백
}
public void close() throws Exception {
// 소멸 전 콜백
}
}
@Configuration
class LifecycleConfiguration {
// initMethod 속성에 초기화 콜백을, destroyMethod에 소멸 전 콜백을 설정할 수 있습니다.
@Bean(initMethod = "init", destroyMethod = "close")
public LifecycleBean lifecycleBean() {
return new LifecycleBeanWithConfiguration();
}
}
@Bean
애너테이션의 destroyMethod 속성의 기본 값이 inferred로 설정돼 있습니다. 이렇게 되면 스프링 컨테이너가 알아서 close()
, shutdown()
라는 이름을 가진 메서드를 추론해 호출해 줍니다. 그래서 이러한 메서드를 따로 적어주지 않아도 잘 동작하게 됩니다. 추론 기능을 사용하고 싶지 않을 때는 destroyMethod
속성의 값을 길이가 0인 문자열로 설정하면 됩니다.
@PostConstruct
, @PreDestroy
애너테이션
스프링 빈에 @PostConstruct
와 @PreDestroy
애너테이션이 있는 메서드를 통해 생성되고 소멸될 때 실행될 메서드를 지정할 수 있습니다.
애너테이션을 사용하는 방식은 최신 스프링에서 권장하는 방식으로 애너테이션만 붙이면 되므로 매우 편리합니다. 또 애너테이션이 JSR 250이라는 자바 표준이므로 스프링에 의존하지 않습니다.
다만 이 방식은 스프링 빈 내부를 수정해야 하므로 코드를 개발자가 직접 바꿀 수 없는 외부 라이브러리에는 적용할 수 없다는 단점이 존재합니다.
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
public class LifecycleBeanWithAnnotation implements LifecycleBean {
@PostConstruct
public void init() throws Exception {
// 초기화 콜백
}
@PreDestroy
public void close() throws Exception {
// 소멸 전 콜백
}
}
많은 이들은 스프링 빈을 생성한 후 초기화 작업과 스프링 빈이 소멸되기 전 작업을 처리하기 위해 애너테이션 방식을 택하고, 외부 라이브러리의 경우 설정 정보를 추가해 스프링 빈을 생성하도록 하는 방식을 택하고 있습니다.