지난 포스트에서 Container와 Bean 에 대해서 알아볼 수 있었습니다. 이번 포스트에서는 살짝 더 나아가,
Container 안에서 Bean 이 어떤 형태로 관리되고 있는지, 정확히 어떻게 관리되고 있는지 살펴보겠습니다. (약간
의 TMI 같기도..)
내용이 조금 복잡하다고 느끼실 수 있는데, 마지막 요약을 보시면서도 한번 더 정리하시면 어느정도 그림이 그려지실 것 같습니다. Bean 을 직접 제어하는 일은 거의 없으니 너무 신경쓰실 필요는 없습니다.
Application Context
ApplicationContext ac = AnnotationConfigApplicationContext(AppConfig.class);
이전 포스트에서 Spring 에서 관리해주는 Bean 객체들을 모아두는 공간을 Spring Container 라고 하며, 이를 앱
내에서는 ApplicationContext라고 부른다고 정리하였습니다. 그리고 상단 코드와 같이 Container 를 사용하겠다
고 직접 호출할 수 있습니다.
지금까지 객체지향 내용들을 이해하셨다면, ApplicationContext 역시 Interface 역할 객체라는 느낌이 확 오실 것
입니다. 그리고 AnnotationConfigApplicationContext는 이 ApplicationContext 를 구현하는 하나의 방법, 하
나의 구현 객체 입니다. 이 Spring Container에 설정 정보를 처리하는 방법은 정말 다양하기 때문에, 역할과 구현
을 분리해 놓은 모습입니다.
이 ApplicationContext에 대한 대표적인 두 구현 객체입니다. (참고로, 이 구현객체에 대한 지정은 앱에 대한 구성시 스프링이 알아서 어떤 것을 사용중인지 판단 후 지정해주는 것으로 보입니다)
Annotation Configuration : 지난 포스트에서 봤던 방법이 Annotation 을 통한 등록 방법입니다. 앱이 실행시점에 @Configuration 이라는 annotation 이 붙은 설정 class 를 통해 Container 내에서 관리해야할 Bean 들을 등록해 놓게 됩니다.
XML Configuration : XML 문서를 통해서 Bean 들을 등록하는 방법입니다. Annotation 보다 직관성이 떨어지기 때문에 요즘은 잘 사용되지 않는 방법입니다.
스프링 컨테이너에는 스프링 빈 저장소가 있습니다. 빈 이름과 빈 객체를 매핑하여 저장시켜주는 공간으로, 이 공간
에 등재된 빈 들은 앞으로 해당 빈의 주입이 필요한 클라이언트들에게 제공되게 됩니다.

스프링 컨테이너가 Initialize 되면서 사용하는 방식을 통해 빈 메타 데이터를 형성하게 되는데 (아래 나옵니다), 형성을 마치면 필요한 빈들을 알게 되고, 스프링 빈 저장소에 필요한 빈들을 실제로 저장하는 단계가 진행됩니다.
이 때, 스프링에서 빈 저장소에 빈을 생성하는 것과, 이후 의존관계를 주입하는 단계가 나뉘어져 있다는 점을 명심
하면 좋습니다. 지금까지 한 방법, 즉 생성자를 통해서 진행하게 된다면 이 두 과정은 동시에 일어나게 되지만, 원래
는 두 단계가 나뉘어져 있습니다. "갑자기 뭔소리지?" 싶으실 수 있으니, 일단 알아만 두시고, 나중에 더 자세히 살
펴보겠습니다.
Bean Factory

Bean Factory 는 스프링 컨테이너의 최상위 인터페이스이고, 스프링 빈을 관리하고, 조회하는 핵심 코드들이 구성
되어 있는 공간입니다(getBean(), getDividedNames() ... ). 즉, 스프링 컨테이너인 ApplicationContext 는
Bean 을 관리하기 위한 공간이며, 해당 공간에서 사용하는 메인 로직 함수들이 BeanFactory 최상위 인터페이스에
존재한다고 보면 됩니다 (.. BeanFactory나, ApplicationContext 나 모두 Spring Container 입니다)
ApplicationContext는 당연히 BeanFactory 의 모든 기능을 상속받아서 사용할 수 있습니다. 위에서 말씀드린 것
처럼 핵심 기능들은 BeanFactory 에서 제공이 되지만, ApplicationContext 역시 빈 관리 측면에서 추가 기능 로직
들이 구현되어 있기에 하나의 객체로 존재하고 있는데, 다음과 같은 내용들이 있다 정도만 알아두시면 될 것 같습니
다(거의 쓸 일 없으실 것..).
(1) MessageSource
한국어, 영어 등 언어 기능을 지원합니다
(2) EnvironmentCapable
로컬(PC), 개발(Test Server), 운영 환경(실제 Production) 등 환경 변수 관련된 변수들에 대한 처리를 도와줍니다
(3) ApplcationEventPublisher
이벤트를 발행하고, 구독하는 모델을 편리하게 지원합니다
(4) ResourceLoader
내부적으로 편리한 리소스 조회와 특정 파일 같은 외부 리소스들도 편리하게 조회할 수 있도록 도와줍니다.
어쨌든 우리가 앞으로 빈에 관련되어 사용할 메인 로직들은 대부분 BeanFactory의 핵심 로직들이며, 위와 같은 기
능 로직들을 Application Context가 제공해주게 됩니다 (기능 로직들 관련해서는 추후 기회가 되면 정리해보겠습
니다).
빈 설정 메타 정보 (Bean Definition)

위에서 ApplicationContext 를 구현하기 위해서 Annotation을 사용하기도 하고, Xml을 사용하는 방식도 있으며
다양한 방식을 지원한다고 언급하였습니다. 여기서 추상적으로 구현한다고 설명드렸었는데, 이 때 만드는 정보가
바로 빈 설정 메타 정보, 바로 Bean Definition 값입니다.
각 설정 파일들을 읽을 때 해당 파일의 방식 (Java : AppConfig.class , XML : appConfig.xml 등등)에 따라
AppliationContext 는 자신의 구현객체를 붙입니다.
---
(구현객체 사진 + Bean Reader 사진)
---
그리고 위 사진과 같이 각 구현 객체에서 사용할 수 있는 BeanDefinitionReader 를 사용해서 Configuration 파일
을 읽고 BeanDefinition을 생성하게 됩니다. (빈 메타정보 형성 이후에 실제 빈 생성이 이루어집니다)
사실 여기서도 Spring이 적용한 역할과 구현의 분리를 잘 확인할 수 있습니다. ApplicationContext는 어떤 수단
(Java, XML 등등) 을 사용하든지 Bean Definition 만 만들면 되기 때문에, 자신의 구현객체가 어떤 방식으로
BeanDefinition 을 만들었는지 알 필요가 없는 구조입니다.

점점 복잡해지죠..? Spring Container(Application Context)에 빈 저장소가 있다하다가, Spring Container 상위
개념은 Bean Factory 라고 하다가, 이젠 갑자기 Bean Definition 이 저장된다 합니다. 한번 정리해볼게욥..
BeanDefinition? Bean 저장소? BeanFactory?
---
(하단 블로그 그림, 한쪽만)
---
Bean Factory 에는 핵심 로직들이 있으며, 이 핵심 로직들을 ApplicationContext 가 상속받고, 추가적인 기능 로
직들을 제공한다고 설명드렸습니다. 이에 따라 ApplicationContext 는 BeanFactory의 공간을 가지고 있고, 추가
적인 기능들을 가지고 있는 위의 그림처럼 이해해볼 수 있습니다.
BeanFactory 에는 BeanDefinitionMap과 SingletonObjects 라는 공간 두개가 있다고 정리해볼 수 있습니다.
SingletonObjects 란 공간이 위에서 언급한 Bean 저장소이며, methodName 과 실제 저장하는 bean 객체를 매
핑하여 저장해두는 공간입니다.
또한 위에서 ApplicationContext 구현 객체에 붙어 있는 Reader가 읽으면서 형성한 BeanDefinition 객체들은 또
다른 Map 공간에 동일한 methodName 과 매핑되어 저장되는데, 이 공간이 바로 BeanDefinitionMap 입니다.
위 그림을 보시니 어느정도 이해가 되시죠...?
Bean Definition 정보들 살펴보기
@Test
@DisplayName("빈 메타 정보 확인")
void beanDefinitions(){
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanName);
if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
System.out.println("beanDefinition = " + beanDefinition);
}
}
}
Result
beanDefinition = Generic bean: class [com.example.basicprinciple.config.AppSpringConfig$$EnhancerBySpringCGLIB$$727ea2c]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null
beanDefinition = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=appSpringConfig; factoryMethodName=memberRepository2; initMethodName=null; destroyMethodName=(inferred); defined in com.example.basicprinciple.config.AppSpringConfig
beanDefinition = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=appSpringConfig; factoryMethodName=memberService; initMethodName=null; destroyMethodName=(inferred); defined in com.example.basicprinciple.config.AppSpringConfig
beanDefinition = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=appSpringConfig; factoryMethodName=discountPolicy2; initMethodName=null; destroyMethodName=(inferred); defined in com.example.basicprinciple.config.AppSpringConfig
beanDefinition = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=appSpringConfig; factoryMethodName=orderService; initMethodName=null; destroyMethodName=(inferred); defined in com.example.basicprinciple.config.AppSpringConfig
위와 같이 Bean Definition Map 에서 각 BeanDefinition을 찾아서 조회해보면, Result 와 같은 결과들이 출력되는 모습을 확인할 수 있습니다. 저장되어 있는 정보들은 다음 정도와 같다고 생각하면 될 것 같습니다.
BeanClassName : 생성하는 빈의 Class Name
FactoryBeanName : 팩토리 역할의 빈을 사용할 경우 이름 (ex: appConfig)
FactoryMethodName : 빈을 생성할 팩토리 메소드 지정 (ex: memberRepository)
Scope : 빈의 종류
LazyInit : 스프링 컨테이너 생성시 빈을 생성하는게 아니라, 실제 빈을 사용할 때 생성하는 방식 (생성 지연)
InitMethodName : 빈 생성과 의존관계 적용 뒤 호출되는 초기화 메서드 이름
DestroyMethodName : 빈 생명주기 종료시 제거하기 전에 호출되는 메서드 이름
Constructor arguments, properties : 의존관계 주입 관련
Role_Infrastructure : Spring 유지를 위해 자체적으로 사용하기 위해서 관리중인 Bean
Role_Application : 유저가 추가한 Bean
Scope, InitMethod, Destroy Method 같은 특성들은 나중에 더 자세히 다뤄보도록 하겠습니다. 실무에서
BeanDefinition 을 직접 제어할 일은 거의 없기 때문에, 너무 신경쓰지 않으셔도 됩니다. 다만 스프링의 동작
원리에 대해서는 이해하면 이해할수록 개인적으로 좋은 것 같습니다.
포스트 요약
- Application Context 는 Spring Container라고 하며, Annotation, Java, Xml 등 다양한 방법으로 설정이 된다.
- 설정되는 방법에 따라 Reader Class 를 구현하는데, 그 리더로 Configuration 정보를 읽어 BeanDefinition을 형성한다.
- BeanDefinition 형성을 마치면 빈 생성 과정이 이루어지는데, 빈 생성은 생성 + 의존성 주입 두가지 단계로 나뉜다
- BeanFactory 는 Application Context 보다 상위 객체이며, 컨테이너를 다루는 핵심 로직을 가지고 있다.
- Application Context 내부에는 Bean Factory 공간이 있는데, 해당 공간에 BeanDefinition을 저장하는 Map과 실제 싱글톤으로 관리할 Bean 객체들을 저장하는 Map 공간이 있다.
- BeanDefinition에는 다양한 정보가 저장된다..
출처
[스프링 기본]으로 엮인 모든 포스트들은 교육 사이트 인프런의 지식공유자이신 김영한님의 [스프링 핵심 원리] 강의를 기반으로 작성되었습니다. 열심히 정리하고 스스로 공부하기 위해 만든 포스트이지만, 제대로 공부하고 싶으시면 해당 강의를 꼭 들으시는 것을 추천드립니다.
스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...
www.inflearn.com
제가 상단에 조회되는 많은 블로그를 봤지만, Bean 과 Spring Container 에 대해 기술된 블로그 중에는 이 곳이 최고입니다
https://leejaengjaeng.tistory.com/13
Spring Doc - Core -IOC Container
Spring framework Document 읽기 현재 담당하는 서비스가 Spring5를 사용하진 않지만 5의 문서를 읽으면서 추가된 내용이 아니라 Spring framework 자체의 내용에 집중해볼 예정입니다. Docs : https://docs.spring.io/s
leejaengjaeng.tistory.com
'Spring > Spring 기본' 카테고리의 다른 글
| [Spring 기본] Component Scan을 통한 Bean 자동화 관리 (0) | 2022.10.17 |
|---|---|
| [Spring 기본] Spring Container의 Singleton 전략 (0) | 2022.10.14 |
| [Spring 기본] Spring의 객체 지향 IoC, DI, Bean, Container (0) | 2022.10.13 |
| [Spring 기본] 앱 기본 설계, Config 클래스 및 주입의 시작 (0) | 2022.10.13 |
| [Spring 기본] Spring을 시작하며 (0) | 2022.10.12 |