- spring-frameworkioc-containerbean-lifecycledependency-injection
Spring IoC Container와 Bean Lifecycle
Spring Framework는 Annotation을 이용해 IoC Container가 관리해야 할 객체를 구분한다. 또한 객체를 직접 생성(new)하는 주체는 개발자가 아니라 Spring IoC Container이다. 애플리케이션이 실행되면 IoC Container는 Bean을 생성하고 의존성을 주입한 뒤 애플리케이션이 종료될 때까지 Bean의 생명주기를 관리한다. Bean의 생성, 의존성 주입, 초기화, 사용, 종료까지의 전체 과정을 Bean Lifecycle이라고 한다.
IoC Container 생성 (ApplicationContext)
Spring 애플리케이션이 시작되면 ApplicationContext가 생성된다. ApplicationContext는 Spring IoC Container의 대표적인 구현체로 다음 역할을 수행한다.
- Bean 탐색
- Bean 정의 정보 관리
- 객체 생성
- 의존성 주입
- Bean 생명주기 관리
이 컨테이너가 애플리케이션 전반의 객체 생성을 관리한다.
Annotation 기반 Bean 등록
개발자는 Spring이 관리할 객체에 Annotation을 붙여 Bean으로 등록할 수 있다. 대표적으로 다음과 같은 Annotation이 사용된다.
- @Controller
- @Service
- @Repository
- @Component
예를 들어 다음과 같이 Annotation이 붙은 클래스는 Spring이 관리하는 Bean 후보가 된다.
@Service
public class OrderService {
}
@Controller
public class OrderController {
}Bean 탐색 (Component Scan)과 BeanDefinition 등록
Spring 애플리케이션이 시작되면 설정된 패키지를 스캔하여 Bean 후보를 찾는다. 이 과정을 Component Scan이라고 한다. Spring은 다음 Annotation이 붙은 클래스를 Bean 후보로 인식한다.
- @Controller
- @Service
- @Repository
- @Component
발견된 클래스는 BeanDefinition이라는 형태로 IoC Container에 등록된다. BeanDefinition에는 다음과 같은 정보가 포함된다.
- Bean 클래스 정보
- Bean 이름
- Bean 스코프
- 객체 생성 방식
- 초기화 및 종료 설정
이 단계에서는 실제 객체가 생성되지 않는다. 단지 Bean에 대한 정의 정보만 등록된다.
Bean 객체 생성
BeanDefinition 등록이 완료되면 Spring IoC Container는 실제 객체를 생성한다. 객체 생성은 리플렉션 기반으로 수행된다. 예를 들어 다음과 같은 클래스가 있다고 가정해 보자.
@Service
public class OrderService {
}Spring은 내부적으로 다음과 같은 방식으로 객체를 생성한다.
new OrderService()하지만 이 과정은 개발자가 직접 호출하는 것이 아니라 Spring IoC Container가 관리한다.
의존성 주입 (Dependency Injection)
객체가 생성된 이후 Spring은 필요한 의존성을 주입한다. 예를 들어 다음과 같은 코드가 있다고 가정해 보자.
@Controller
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
}Spring은 다음 순서로 동작한다.
- OrderService Bean 생성
- OrderController Bean 생성
- OrderController 생성자를 호출하면서 OrderService 주입
이 과정이 Dependency Injection이다.
Bean 초기화 단계
의존성 주입이 완료된 이후 Bean은 초기화 과정을 거친다. 이 단계에서는 다음과 같은 작업을 수행할 수 있다.
- 데이터 초기화
- 외부 시스템 연결
- 캐시 로딩
Spring에서는 여러 방법으로 초기화 메서드를 정의할 수 있다. 예를 들어 다음과 같이 @PostConstruct가 붙은 메서드는 의존성 주입이 완료된 이후 자동으로 실행된다.
@PostConstruct
public void init() {
System.out.println("Bean 초기화");
}프록시 객체 생성 (BeanPostProcessor 단계)
Bean 초기화 이후 Spring은 BeanPostProcessor라는 확장 구조를 통해 Bean을 추가로 처리할 수 있다. 이 과정에서 특정 Annotation이 붙은 Bean은 실제 객체 대신 Proxy 객체로 감싸질 수 있다. 대표적인 Annotation은 다음과 같다.
- @Transactional
- @Async
- @Cacheable
예를 들어 다음과 같은 코드가 있다고 가정해 보자.
@Transactional
@Service
public class OrderService {
}이 경우 Spring은 다음과 같은 구조로 객체를 구성할 수 있다.
Controller → Proxy(Service) → 실제 ServiceController는 자신이 받은 객체가 실제 객체인지 Proxy 객체인지 신경 쓰지 않고 동일하게 메서드를 호출할 수 있다. Proxy 객체는 메서드 실행 전후에 트랜잭션 처리나 부가 기능을 수행한다. 이러한 Proxy 생성 과정은 대부분 BeanPostProcessor의 postProcessAfterInitialization 단계에서 수행된다.
Bean 저장 (IoC Container 관리)
객체 생성, 의존성 주입, 초기화, Proxy 생성 과정이 완료되면 Bean은 IoC Container 내부에 저장된다. 기본적으로 Spring Bean은 Singleton Scope로 관리된다. 즉 애플리케이션 전체에서 하나의 동일한 객체가 재사용된다.
Bean 사용
Spring MVC 애플리케이션에서는 DispatcherServlet이 웹 요청을 받는다. DispatcherServlet은 IoC Container에서 적절한 Controller Bean을 찾아 해당 메서드를 실행한다. 즉 개발자는 직접 객체를 생성하지 않는다. 모든 객체 생성과 의존성 관리는 Spring IoC Container가 담당한다.
Bean 소멸 단계
애플리케이션이 종료되면 Spring Container는 Bean의 종료 과정을 수행한다. 이 단계에서는 다음과 같은 작업을 수행할 수 있다.
- 데이터베이스 연결 종료
- 네트워크 리소스 정리
- 캐시 저장
종료 메서드는 다음과 같이 정의할 수 있다.
@PreDestroy
public void destroy() {
System.out.println("Bean 종료");
}Spring Container가 종료될 때 자동으로 실행된다.
정리하면 Spring IoC Container는 다음과 같은 Bean Lifecycle 단계를 관리한다.
- Component Scan으로 Bean 후보 탐색
- BeanDefinition 생성
- Bean 객체 생성
- Dependency Injection 수행
- Bean 초기화
- 필요 시 Proxy 생성
- IoC Container에 Bean 저장
- 애플리케이션에서 Bean 사용
- 애플리케이션 종료 시 Bean 소멸
이러한 구조 덕분에 개발자는 객체 생성과 의존성 관리 대신 비즈니스 로직 개발에 집중할 수 있다.
(끝)