컨테이너를 코드로 이해하려면 먼저 두 개의 축을 분리해서 봐야 한다.
- 컨테이너 계약(
BeanFactory계층) — "빈을 어떻게 꺼내고 조회하는가"를 정의하는 인터페이스들. 클라이언트가 보는 얼굴과, 설정자가 보는 얼굴, 컨테이너 내부가 보는 얼굴이 인터페이스 분리 원칙(ISP)에 따라 층층이 나뉜다. - 빈 메타데이터(
BeanDefinition) — "어떤 빈을 어떻게 만들지"를 담은 데이터. 실행 코드가 아니라 설정의 표현이며, 따라서 인스턴스화 전에 자유롭게 수정할 수 있다.
이 장은 이 두 축의 타입 지도를 그려, 이후 02~04장의 동작 흐름이 "어느 인터페이스의 어느 메서드"에서 일어나는지 좌표를 제공한다.
┌───────────────────────┐
│ BeanFactory │ 클라이언트 뷰
│ getBean / containsBean │ "빈을 꺼낸다"
│ isSingleton / getType │
└───────────┬───────────┘
┌────────────────────┼─────────────────────┐
▼ ▼ ▼
┌─────────────────────┐ ┌──────────────────┐ ┌────────────────────────┐
│ HierarchicalBean │ │ ListableBean │ │ AutowireCapableBean │
│ Factory │ │ Factory │ │ Factory │
│ getParentBeanFactory│ │ 모든 빈 이름 열거 │ │ 컨테이너 밖 객체에 │
│ (부모-자식 컨테이너) │ │ getBeanNamesForType│ │ DI/초기화 적용 │
└──────────┬──────────┘ └────────┬─────────┘ └───────────┬────────────┘
│ +SingletonBeanRegistry │ │
▼ │ │
┌─────────────────────┐ │ │
│ ConfigurableBean │ │ │
│ Factory │ 설정자 뷰 │ │
│ addBeanPostProcessor │ registerScope │ │
│ setConversionService │ registerAlias │ │
└──────────┬──────────┘ │ │
│ ┌─────────────────────────────────────────┘
▼ ▼ ▼
┌───────────────────────────────────────────┐
│ ConfigurableListableBeanFactory │ 내부 종합 뷰
│ getBeanDefinition / preInstantiateSingletons│
│ ignoreDependencyType / freezeConfiguration │
└───────────────────────────────────────────┘
각 인터페이스의 존재 이유:
BeanFactory— 가장 기본인 "빈 꺼내기" 계약.getBean(name),getBean(Class),getBeanProvider(...),containsBean,isSingleton/isPrototype,getType. 여기 정의된String FACTORY_BEAN_PREFIX = "&"는FactoryBean자체를 꺼낼 때 쓰는 접두사다(&myFactory→ 팩토리 자신,myFactory→ 팩토리가 만든 객체).HierarchicalBeanFactory— 부모 컨테이너를 둘 수 있게 한다.getBean은 자식에서 못 찾으면 부모에 위임한다(자식이 같은 이름을 가리면 자식 우선).ListableBeanFactory— 타입으로 빈을 열거한다(getBeanNamesForType,getBeansOfType). 단, 부모는 보지 않고 이 팩토리만 본다(전수 조회는BeanFactoryUtils가 보강).ConfigurableBeanFactory— 컨테이너를 구성한다.BeanPostProcessor등록, 스코프 등록,ConversionService/EmbeddedValueResolver설정, 빈 소멸.SingletonBeanRegistry도 상속해 외부 싱글톤을 수동 등록할 수 있다.AutowireCapableBeanFactory— 컨테이너가 관리하지 않는 객체에도 DI·초기화·BeanPostProcessor를 적용한다. 서블릿이나 JPA 엔티티처럼 컨테이너 밖에서 생성된 객체에 의존성을 주입할 때 쓰인다(autowireBean,initializeBean).ConfigurableListableBeanFactory— 위 모두를 합친 내부용 종합 인터페이스.getBeanDefinition, 비-지연 싱글톤 일괄 생성(preInstantiateSingletons), 설정 동결(freezeConfiguration).
FactoryBeanRegistrySupport
│ (FactoryBean 결과 캐싱)
▼
AbstractBeanFactory implements ConfigurableBeanFactory
│ · doGetBean 템플릿 (스코프 분기, depends-on)
│ · getMergedLocalBeanDefinition (부모-자식 정의 병합)
│ · BeanPostProcessor 목록 보관
▼
AbstractAutowireCapableBeanFactory implements AutowireCapableBeanFactory
│ · createBean / doCreateBean (인스턴스화·주입·초기화)
│ · populateBean, initializeBean, autowireByType/Name
▼
DefaultListableBeanFactory ★ implements ConfigurableListableBeanFactory,
│ BeanDefinitionRegistry
│ · BeanDefinition 레지스트리(Map<String,BeanDefinition>)
│ · resolveDependency (타입 기반 autowiring의 본체)
│ · preInstantiateSingletons
세 클래스는 템플릿 메서드 패턴으로 역할을 나눈다. 위쪽은 "조회/스코프/수명주기의 뼈대"를, 가운데는 "한 빈을 실제로 만드는 절차"를, 맨 아래 DefaultListableBeanFactory는 "정의를 저장하고 타입으로 후보를 찾는" 레지스트리 역할을 더한다. DefaultSingletonBeanRegistry(03장)는 FactoryBeanRegistrySupport의 상위로서 싱글톤 캐시를 제공한다.
BeanDefinition은 AttributeAccessor, BeanMetadataElement를 상속한 순수 데이터 인터페이스다. 클래스 이름, 스코프, 생성자 인자(ConstructorArgumentValues), 프로퍼티 값(MutablePropertyValues), init/destroy 메서드 이름, lazy/primary/fallback 같은 플래그를 담는다.
BeanDefinition (interface)
▲ ▲
│ │
AbstractBeanDefinition (공통 상태/기본값)
▲ ▲ ▲
│ │ │
RootBean ChildBean GenericBean
Definition Definition Definition
│ │ │
│ 부모 참조 │ 범용(파싱· │
│ 병합 결과 │ 프로그래밍 등록)
GenericBeanDefinition— 설정 시점에 쓰는 범용 정의. 부모 이름을 문자열로 들고 있다가 나중에 병합된다.RootBeanDefinition— 런타임에 병합된 최종 정의. 컨테이너는 빈을 만들기 직전 부모-자식 정의를 합쳐RootBeanDefinition(흔히mbd= merged bean definition)을 만든다. 여기에는 해소된 타입(resolvedTargetType), 해소된 생성자(resolvedConstructorOrFactoryMethod) 같은 런타임 캐시 필드도 붙는다.ChildBeanDefinition— 부모를 명시적으로 가리키는 자식 정의(요즘은GenericBeanDefinition+setParentName을 더 많이 쓴다).
BeanDefinition이 인터페이스이고 수정 가능하다는 점이 핵심 설계다. 덕분에 BeanFactoryPostProcessor가 인스턴스화 전에 정의를 들여다보고 프로퍼티를 바꿔치기할 수 있다(예: @Value("${...}") 자리표시자 치환). 자세한 건 06장.
getBean("foo") 한 번이 계층을 어떻게 타고 내려가는지 한눈에:
client.getBean("foo")
│ (BeanFactory)
▼
AbstractBeanFactory.doGetBean("foo") ◀ 템플릿 메서드
│ 1. transformedBeanName: 별칭·"&" 접두사 해소
│ 2. getSingleton("foo") → 캐시 히트면 즉시 반환
│ 3. 부모 위임 여부 판단 (Hierarchical)
│ 4. getMergedLocalBeanDefinition("foo") → RootBeanDefinition
│ 5. 스코프 분기 (singleton / prototype / custom)
▼
AbstractAutowireCapableBeanFactory.createBean(mbd) ◀ 실제 생성 (02장)
│
▼
DefaultListableBeanFactory.resolveDependency(...) ◀ 주입 대상 찾기 (04장)
doGetBean은 자신이 직접 객체를 만들지 않는다. 스코프를 판단해 "싱글톤이면 캐시 통해, 프로토타입이면 매번, 커스텀 스코프면 Scope.get() 통해" createBean을 언제 호출할지만 조율한다. 객체를 만드는 책임은 하위 클래스(createBean)로 위임된다 — 전형적인 템플릿 메서드 패턴.
AbstractBeanFactory.transformedBeanName(name)— 입력 이름을 정규화한다.&(FactoryBean) 접두사를 떼고, 별칭(alias)을 표준 이름으로 바꾼다. 모든 조회의 첫 단계.getMergedLocalBeanDefinition(beanName)— 부모 정의가 있으면 부모-자식을 재귀적으로 병합해RootBeanDefinition을 만들고 캐시한다. 이후 모든 생성/타입 판단은 이 병합 정의를 본다.BeanFactory.getBeanProvider(Class)— 즉시 꺼내지 않고 지연 핸들(ObjectProvider)을 돌려준다. 선택적 의존성(getIfAvailable), 유일성 검사(getIfUnique), 스트리밍(orderedStream)을 지원해@Autowired Optional<X>/List<X>같은 시나리오의 프로그래밍 버전을 제공한다.
- 인터페이스 분리(ISP) — 같은
DefaultListableBeanFactory인스턴스라도, 애플리케이션 코드에는BeanFactory로, 후처리기에는ConfigurableListableBeanFactory로, 외부 객체 주입에는AutowireCapableBeanFactory로 다른 얼굴을 보여줄 수 있다. - 메타데이터와 실행의 분리 —
BeanDefinition이 데이터이기에, 설정 소스(XML/애너테이션/Java/Groovy)가 무엇이든 결국 같은BeanDefinition으로 수렴한다. 컨테이너 코어는 설정 형식을 모른다. FactoryBean— 빈 생성 로직 자체를 빈으로 만드는 훅.getObject()가 반환하는 것이 실제 노출 빈이 된다(프록시 팩토리, JNDI 등 프레임워크가 애용).
spring-beans의 골격은 "계약 계층(BeanFactory→ConfigurableListableBeanFactory)"과 "구현 계층(AbstractBeanFactory→DefaultListableBeanFactory)", 그리고 그 사이를 흐르는 데이터인 "BeanDefinition"의 3요소로 이해할 수 있다. 클라이언트는 getBean만 보지만, 그 한 줄은 별칭 해소 → 정의 병합 → 스코프 분기 → 생성 → 의존성 해소라는 잘 짜인 템플릿을 따라 내려간다. 다음 장은 그 "생성" 단계를 현미경으로 본다.