spring
스프링 프레임워크에서 “스프링 빈(Bean)”이란, IoC(Inversion of Control) 컨테이너가 직접 관리하는 객체를 말합니다. @Component, @Service, @Repository, @Controller 등과 같이 컴포넌트 스캔 대상으로 등록되거나, 자바 설정(@Configuration, @Bean)을 통해 생성되어 IoC 컨테이너에 의해 관리되는 객체들이 스프링 빈이 됩니다.
스프링은 이 빈들을 생성, 의존성 주입, 초기화, 사용, 종료의 과정을 거쳐서 lifecycle을 관리합니다. 이러한 과정을 “스프링 빈 생명주기(Bean Lifecycle)”라고 부르며, 스프링은 이 생명주기에 개입할 수 있는 여러 가지 “콜백(callback) 지점”을 제공합니다.
스프링 빈의 생명주기는 크게 다음과 같은 단계를 거칩니다.
이번 글에서는 특히 초기화(Initialization) 콜백과 소멸(Destruction) 콜백을 중심으로, 각 단계에서 어떤 일이 일어나는지 구체적으로 살펴보겠습니다.
스프링은 빈의 생명주기에 다양한 지점에서 콜백 로직을 주입할 수 있도록 여러 기법을 제공합니다. 대표적으로 아래 네 가지를 주로 사용합니다.
이 네 가지 방법은 사용 목적과 편의성 면에서 차이가 있습니다. 가장 권장되는 방식은 자바 표준 애노테이션인 @PostConstruct, @PreDestroy를 활용하는 것입니다. 이유는 다음과 같습니다:
나머지 방법들도 상황에 따라 사용할 수 있으니 각각의 특성을 알아봅시다.
스프링에서 가장 권장하는 초기화 방식으로, 다음과 같이 사용할 수 있습니다.
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Component;
@Component
public class ExampleService {
public ExampleService() {
System.out.println("ExampleService 생성자 호출");
}
@PostConstruct
public void init() {
System.out.println("ExampleService init() 호출 - 의존성 주입 완료 후 초기화 작업");
}
// @PreDestroy는 소멸 콜백에서 설명
}
Java@PostConstruct가 붙은 메서드는 빈 생성 및 의존관계 주입이 완료된 직후 호출됩니다.
InitializingBean 인터페이스의 afterPropertiesSet()을 구현하는 방법도 있습니다.
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
@Component
public class ExampleService implements InitializingBean {
public ExampleService() {
System.out.println("ExampleService 생성자 호출");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("ExampleService.afterPropertiesSet() 호출");
}
}
Java@Bean 애노테이션을 사용해 메서드를 스프링 빈으로 등록하면서, 직접 초기화 메서드를 지정할 수도 있습니다.
@Configuration
public class AppConfig {
@Bean(initMethod = "init")
public ExampleService exampleService() {
return new ExampleService();
}
}
JavaExampleService 클래스 안에서 init() 메서드를 만든 뒤 다음과 같이 작성합니다.
public class ExampleService {
public void init() {
System.out.println("ExampleService init() 호출 - 자바 설정에서 initMethod 지정");
}
}
Java빈이 더 이상 필요 없어지고 컨테이너가 종료되거나, 수동으로 빈을 제거해야 하는 상황에서 소멸 콜백이 호출됩니다.
@PostConstruct와 짝을 이루는 @PreDestroy 애노테이션입니다.
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Component;
@Component
public class ExampleService {
@PostConstruct
public void init() {
System.out.println("초기화 로직 실행");
}
@PreDestroy
public void destroy() {
System.out.println("소멸 로직 실행 - 자원 반납 등 정리");
}
}
JavaDisposableBean은 destroy() 메서드를 오버라이드하여 소멸 로직을 작성합니다.
import org.springframework.beans.factory.DisposableBean;
public class ExampleService implements DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("ExampleService.destroy() 호출");
}
}
Java자바 설정이나 XML 설정에서 destroyMethod를 지정할 수도 있습니다.
@Configuration
public class AppConfig {
@Bean(initMethod = "init", destroyMethod = "cleanup")
public ExampleService exampleService() {
return new ExampleService();
}
}
JavaExampleService 안에 cleanup() 메서드를 두어 자원 정리를 합니다.
public class ExampleService {
public void cleanup() {
System.out.println("cleanup() 호출 - destroyMethod에서 지정");
}
}
Java초기화(Initialization)와 소멸(Destruction) 콜백 외에도, 스프링은 빈의 생명주기에 개입할 수 있는 후처리기(BeanPostProcessor) 개념을 제공합니다.
BeanPostProcessor를 구현하면, 스프링 빈이 생성된 직후(postProcessBeforeInitialization)와 초기화 직후(postProcessAfterInitialization) 등 여러 시점에 후처리 로직을 삽입할 수 있습니다.
대표적인 사용 예시
예시 코드
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
// 초기화(beforeInitialization) 직전 콜백
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof ExampleService) {
System.out.println("[BeanPostProcessor] " + beanName + " 초기화 전 로직 실행");
}
return bean;
}
// 초기화(afterInitialization) 직후 콜백
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof ExampleService) {
System.out.println("[BeanPostProcessor] " + beanName + " 초기화 후 로직 실행");
}
return bean;
}
}
JavaBeanPostProcessor를 활용하면 특정 빈을 대상으로 동적 프록시를 생성해 AOP 처리를 하거나, 추가 검증 로직을 넣는 등 다양한 기능을 구현할 수 있습니다.
헷갈리기 쉬운 또 다른 인터페이스로 BeanFactoryPostProcessor가 있습니다. 이는 “빈 공장(BeanFactory)” 자체를 후처리하는 로직을 넣을 수 있는 방법으로, 실제 빈 인스턴스가 아닌 빈 정의(BeanDefinition) 정보를 다룹니다.
반면에 BeanPostProcessor는 빈 인스턴스(실체 객체)가 생성된 후를 다루는 것이 차이점입니다.
여기서는 @Configuration 클래스와 @PostConstruct, @PreDestroy를 함께 사용하는 예시 코드를 보여드리겠습니다.
// ExampleService.java
package com.example.lifecycle;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class ExampleService {
public ExampleService() {
System.out.println("ExampleService 생성자 호출");
}
@PostConstruct
public void init() {
System.out.println("ExampleService init() 호출");
// 초기화 시 필요한 로직 (예: DB 연결, 캐시 로딩 등)
}
public void doSomething() {
System.out.println("ExampleService.doSomething() 실행");
}
@PreDestroy
public void cleanup() {
System.out.println("ExampleService cleanup() 호출");
// 소멸 시 필요한 로직 (예: 리소스 해제, 스레드 종료 등)
}
}
Java// AppConfig.java
package com.example.lifecycle;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public ExampleService exampleService() {
return new ExampleService();
}
}
Java// Main.java
package com.example.lifecycle;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
System.out.println("===> 스프링 컨테이너 생성");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println("===> 스프링 컨테이너에서 ExampleService 가져오기");
ExampleService service = context.getBean(ExampleService.class);
service.doSomething();
System.out.println("===> 스프링 컨테이너 종료");
context.close();
}
}
Java===> 스프링 컨테이너 생성
ExampleService 생성자 호출
ExampleService init() 호출
===> 스프링 컨테이너에서 ExampleService 가져오기
ExampleService.doSomething() 실행
===> 스프링 컨테이너 종료
ExampleService cleanup() 호출
Java스프링 빈 생명주기와 콜백을 잘 이해하고 사용하면, 애플리케이션 전반에 걸쳐 더 견고하고 일관성 있는 초기화와 자원 관리를 할 수 있습니다.
가장 핵심적인 포인트는 다음과 같습니다.
위 내용을 숙지하면, 스프링 빈 관리에 대해 더 깊이 있게 이해하고 체계적으로 애플리케이션을 구성할 수 있을 것입니다. 스프링은 애플리케이션 전반을 유연하게 구성해주기 때문에, Bean Lifecycle에 대한 이해는 필수적입니다. 꼭 알아두시고 다양한 프로젝트에서 활용해 보세요!
Readable Code: 읽기 좋은 코드를 작성하는 사고법Practical Testing: 실용적인 테스트 가이드 강의와 함께한 인프런 워밍업 클럽…
Readable Code: 읽기 좋은 코드를 작성하는 사고법Practical Testing: 실용적인 테스트 가이드 강의와 함께한 인프런 워밍업 클럽…
테스트 시 의존성 주입(Dependency Injection)과 Mockito Spring 애플리케이션을 개발하다 보면, 테스트 코드에서 실제 빈(Bean)을 사용하지…
들어가며 스프링 기반 프로젝트에서 좋은 설계 구조와 테스트 전략은 소프트웨어 품질과 유지보수성에 직결됩니다. 최근 학습한…
Readable Code: 읽기 좋은 코드를 작성하는 사고법Practical Testing: 실용적인 테스트 가이드 강의와 함께한 인프런 워밍업 클럽…
Readable Code: 읽기 좋은 코드를 작성하는 사고법Practical Testing: 실용적인 테스트 가이드 강의와 함께한 인프런 워밍업 클럽…