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에 대한 이해는 필수적입니다. 꼭 알아두시고 다양한 프로젝트에서 활용해 보세요!
인터넷 네트워크란? "인터넷(Internet)"이라는 단어는 "인터네트워크(Internetwork)"의 줄임말입니다. 즉, 여러 개의 네트워크가 상호 연결되어 전 세계적으로 하나의…
스프링 빈(Spring Bean)과 IoC 컨테이너 스프링 프레임워크의 핵심 철학은 IoC(Inversion of Control) 컨테이너를 통해 객체(빈,…
의존성 주입(Dependency Injection)이란? 프로그램을 개발하다 보면, 여러 클래스나 객체들은 서로 필요한 기능을 사용하기 위해 관계를…
컴포넌트 스캔이란? 컴포넌트 스캔(Component Scan)은 스프링 프레임워크가 특정 패키지를 탐색하면서, 스캔 대상에 해당하는 클래스를 찾아…
스프링 빈이란? 스프링 빈(Spring Bean)은 스프링 IoC(Inversion of Control) 컨테이너가 관리하는 자바 객체를 의미합니다. 간단히…
스프링 컨테이너(Spring Container)란? 스프링 컨테이너는 스프링 프레임워크에서 가장 핵심적인 부분으로, IoC(Inversion of Control) 개념을 기반으로…