
스프링은 스프링 컨테이너를 이용하여 자바 객체의 생명주기를 관리합니다.
스프링은 스프링 컨테이너로서 BeanFactory 라는 인터페이스르 제공하며 해당 인터페이스는 Bean을 관리하고 검색하는 기능을 제공합니다.
ApplicationContext는 BeanFactory를 상속받은 인터페이스로 Bean의 관리 및 검색 기능 뿐만 아니라 다국화, 이벤트 기능 등 추가적인 기능들을 제공합니다.
그렇기 때문에 대부분 ApplicationContext 인터페이스의 구현체를 사용하여 개발을 진행합니다.
저는 이번에 ApplicationContext에 초첨을 맞춰 글을 써보고자 합니다.
Spring에서는 ApplicationContext의 다양한 구현체를 제공합니다.
ApplicationContext의 사용 목적 중 하는 Bean의 관리 및 검색 기능을 제공하는 것입니다.
구현체에 따라 XML, Java 클래스 파일 등 다양한 형식의 설정 파일을 통해 스프링에서 관리할 자바 객체(Bean)을 추가할 수 있습니다.

스프링에는 위와 같이 몇가지의 ApplicationContext 구현체가 존재한다.
AnnotationConfigApplicationContext: Java 클래스 설정 파일을 기반으로 빈을 관리한다.
GenericXmlApplicationContext: xml 설정 파일을 기반으로 빈을 관리한다.
xxxApplicationContext: Java 혹은 xml 설정 파일 이외에 다른 형식의 설정파일로 빈을 관리한다.
이것이 가능한 이유는 Bean 등록을 BeanDefinition으로 추상화하여 생성하기 때문입니다.
다양한 형식의 설정 파일에 상관없이 BeanDefinition 객체가 생성됩니다.

스프링 컨테이너는 다음과 같은 순서로 생성된다.
만약 동일한 이름의 빈 객체가 존재할 경우 오류가 발생한다.


Bean이 생성되고 의존성이 주입된 후, 스프링 컨테이너는 Bean의 초기화 작업을 수행합니다.
초기화 작업은 커스텀한 초기화 메서드를 호출하거나 특정 인터페이스를 구현한 메서드를 실행하는 등의 방식으로 진행됩니다.
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
// ac.getBean(빈 이름, 타입)
MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
// ac.getBean(타입)
MemberServiceImpl memberService = ac.getBean(MemberServiceImpl.class);
스프링 컨테이너가 종료될 때 또는 필요한 경우에 Bean의 소멸 작업을 수행합니다.
소멸 작업은 커스텀한 소멸 메서드를 호출하거나 특정 인터페이스를 구현한 메서드를 실행하는 등의 방식으로 진행됩니다.
BeanDefinition은 빈 설정 메타정보라고 합니다.

BeanDefinition 라는 인터페이스 덕분에 스프링 컨테이너는 설정 파일이 자바 코드인지, xml인지 몰라도 빈을 관리할 수 있습니다.
BeanDefinition은 역할(interface)이 되고, AppConfig.class, appConfig.xml, appConfig.xxx 등과 같은 것들이 구현이 되어 역할과 구현으로 서로 나눠졌다고 볼 수 있습니다.
BeanDefinition 에 대해 조금 더 자세히 알아보겠습니다.
public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {
private final AnnotatedBeanDefinitionReader reader;
...
}
앞서 자바 설정 구현체 중에는 AnnotationConfigApplicationContext 가 있고 해당 코드를 보면 AnnotatedBeanDefinitionReader 라는 객체가 존재하며 이는 자바 설정 파일을 읽어 BeanDefinition를 생성하는 역할을 합니다.

이 뿐만 아니라 다른 ApplicationContext 의 구현체들도 각각 Reader객체가 존재하고 해당 객체가 설정파일을 읽어 BeanDefinition를 생성합니다.
BeanDefinition에는 다음과 같은 정보들이 포함되어 있습니다.
실제로 BeanDefinition의 메타정보를 출력해보겠습니다.
아래코드는 Java로 작성된 설정파일입니다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
해당 Java 설정파일을 의존성으로 하여 AnnotationConfigApplicationContext를 통해 의존성 컨테이너를 생성하고 개발자가 생성한 빈만(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) 추려 출력하도록 코드를 작성하였습니다.
class BeanDefinitionTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@DisplayName("빈 설정 메타정보 확인")
@Test
void findApplicationBean() {
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
System.out.println("beanDefinitionName = " + beanDefinitionName);
System.out.println("beanDefinition = " + beanDefinition);
}
}
}
factoryBeanName는 AppConfig의 명이 되고, factoryMethodName는 빈 저장소에 저장된 빈의 이름(key)이 출력됩니다.
beanDefinitionName = memberService
beanDefinition = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false;
factoryBeanName=appConfig; factoryMethodName=memberService; initMethodName=null; destroyMethodName=(inferred); defined in hello.core.AppConfig
beanDefinitionName = memberRepository
beanDefinition = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false;
factoryBeanName=appConfig; factoryMethodName=memberRepository; initMethodName=null; destroyMethodName=(inferred); defined in hello.core.AppConfig
해당 포스팅에서 스프링의 ApplicationContext과 이것의 동작과정을 알아보았고, 빈의 메타 정보를 가지고 있는 객체인 BeanDefinition에 대해서 알아보았습니다.
여러가지 형식의 설정 파일을 이용하여 빈을 스프링 컨테이너에 주입할 수 있지만 등록해야 할 빈이 수십개, 수백개가 되면 일일이 등록하기 귀찮고, 설정정보 자체가 커져 누락하는 문제까지 발생할 수 있다.
그래서 스프링은 설정정보 없이 자동으로 스프링 빈을 등록하는 @ComponentScan이라는 기능과 의존관계도 자동으로 주입하는 @Autowired라는 기능을 제공합니다.
해당 어노테이션을 사용하면 특정 범위 내에 있는 자바 클래스들 중 @Component라는 어노테이션이 붙은 클래스에 대해 스프링 컨테이너에서 자동으로 빈을 생성하여 생명주기를 관리해주기 때문에 개발자가 비지니스 로직에 더욱 집중할 수 있습니다. 해당 기능에 대해서는 기회가 되면 포스팅 할 수 있도록 하겠습니다.
긴 글 읽어주셔서 감사합니다.
https://drcode-devblog.tistory.com/334
https://velog.io/@max9106/Spring-ApplicationContext
https://sorryday.tistory.com/21
https://devwarriorjungi.tistory.com/entry/스프링-컨테이너Spring-Container-빈Bean