이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- ApplicationContext는 애플리케이션 시작 시 어떤 순서로 Bean을 준비하는가?
- BeanDefinition은 왜 Bean 인스턴스와 구분해서 이해해야 하는가?
- Spring Boot를 쓰더라도 컨테이너 부팅 흐름을 알아야 하는 이유는 무엇인가?
개요
Spring 공식 문서는 컨테이너가 설정 메타데이터를 받아 Bean을 생성하고 조립한다고 설명한다. 이 설정 메타데이터는 XML, 애너테이션, Java 설정 등 여러 방식으로 제공될 수 있다.
중요한 점은 컨테이너가 설정을 읽자마자 모든 객체를 즉시 만드는 것이 아니라 BeanDefinition이라는 내부 메타데이터를 구성한다는 것이다. BeanDefinition은 Bean을 만들기 위한 설명서이고, 실제 Bean 인스턴스와는 다르다.
원리
ApplicationContext 부팅 흐름을 단순화하면 다음과 같다.
설정 정보 읽기
BeanDefinition 등록
BeanFactoryPostProcessor 실행
BeanPostProcessor 등록
singleton Bean 생성
의존성 주입
초기화 callback 실행
애플리케이션 사용 가능 상태이 흐름은 Spring Boot에서도 사라지지 않는다. Boot는 자동 설정과 starter로 많은 BeanDefinition을 자동 등록하지만, 컨테이너가 정의를 읽고 Bean을 만드는 구조는 그대로다.
BeanDefinition
BeanDefinition에는 다음 정보가 들어간다.
- Bean class
- Bean name
- Scope
- Constructor argument
- Property value
- Lazy 여부
- Init method
- Destroy method
- Factory method 정보
이 정보가 있으므로 컨테이너는 객체를 만들기 전에 정의를 검사하거나 바꿀 수 있다. 예를 들어 PropertySourcesPlaceholderConfigurer 같은 후처리기는 실제 Bean 생성 전 설정 값을 해석하는 데 관여한다.
이 단계의 핵심 확장 포인트가 BeanFactoryPostProcessor다. 이 후처리기는 Bean 인스턴스를 다루는 것이 아니라 BeanDefinition 메타데이터를 다룬다. 그래서 이 시점에 일반 Bean을 강제로 getBean()으로 꺼내면 너무 이른 인스턴스 생성이 일어나고, 이후 적용되어야 할 후처리나 자동 프록시 흐름을 우회할 수 있다.
Singleton 사전 생성
singleton Bean은 기본적으로 컨테이너 시작 시점에 미리 생성된다. 이 동작은 운영 안정성에 중요하다. 설정 오류, 순환 의존성, 누락된 Bean 후보 같은 문제를 첫 요청이 들어온 뒤가 아니라 애플리케이션 시작 시점에 발견할 수 있기 때문이다.
lazy 설정을 사용하면 이 생성 시점을 뒤로 미룰 수 있다. 하지만 시작이 빨라지는 대신 오류 발견도 늦어진다.
실전 팁
- 애플리케이션 시작 실패 로그는 대개 BeanDefinition 등록, 후보 Bean 선택, Bean 생성, 의존성 주입 중 한 지점에서 발생한다.
- 오류 메시지에
NoSuchBeanDefinitionException,NoUniqueBeanDefinitionException,BeanCreationException이 나오면 컨테이너 부팅 단계의 어디에서 실패했는지 먼저 분류한다. - 자동 설정을 이해할 때도 “어떤 BeanDefinition이 조건부로 등록되었는가?”로 보면 흐름이 단순해진다.
- Bean을 직접 조회하는 코드보다 컨테이너가 생성자 주입으로 연결하게 하는 코드가 부팅 실패를 더 명확히 드러낸다.
- BeanFactoryPostProcessor 안에서는 일반 Bean 조회를 피한다. 그 단계는 BeanDefinition을 조정하는 자리이지 애플리케이션 객체를 사용하는 자리가 아니다.
- 시작 시점에 실패하는 것은 불편하지만 좋은 신호일 때가 많다. 런타임 첫 요청에서 실패하는 것보다 훨씬 안전하다.
위험 신호!
- 애플리케이션 부팅 실패를 단순히 “Spring 에러”로만 보고 Bean 생성 단계 분석을 하지 않는다.
- lazy를 전역으로 켜서 시작 오류를 숨긴다.
- 설정 클래스가 너무 복잡해 어떤 BeanDefinition이 등록되는지 추적하기 어렵다.
- Bean 이름 충돌이나 후보 충돌을 우연한 이름 변경으로 해결한다.
- 자동 설정이 만든 Bean과 직접 등록한 Bean의 관계를 확인하지 않는다.
- BeanFactoryPostProcessor에서 일반 Bean을 조회해 조기 초기화를 만든다.
확인 질문
확인 질문
- BeanDefinition과 Bean 인스턴스의 차이는 무엇인가?
- BeanDefinition은 Bean을 만들기 위한 메타데이터이고, Bean 인스턴스는 그 정의를 바탕으로 생성된 실제 객체다.
- singleton Bean을 시작 시점에 미리 만드는 장점은 무엇인가?
- 의존성 누락과 설정 오류를 애플리케이션 시작 시점에 발견할 수 있다.
- Spring Boot에서도 컨테이너 부팅 흐름이 중요한 이유는 무엇인가?
- 자동 설정도 결국 조건에 따라 BeanDefinition을 등록하고 ApplicationContext가 Bean을 생성하는 흐름 위에서 동작하기 때문이다.