Spring IoC Container를 까보자 #BeanFactory와 ApplicationContext

2020. 9. 4. 16:05 Spring Framework/Spring boot

앞서 bean 등록(registry) getBean 메소드 호출 과정을 살펴보았다. 간단하게 다시 정리해보자면

  • bean 등록 : 메타데이터를 통해 BeanDefinition을 Map<String, BeanDefinition>에 저장한다.
  • getBean
    • 처음 호출 
      • GenericBeanDefinition을 RootBeanDefinition으로 재 정의 후 Map<String, BeanDefinition>에 저장
      • bean instance 생성 후 Map<String, Object>에 저장 후 반환
    • 2번 이상 호출
      • Map<String, Object>에 bean instance 꺼내와 반환

  예제로 설명한 AnnotationConfigApplicationContext는 DefaultListableBeanFactory를 통해 대부분에 기능을 위임하고 있는 것을 확인했다. 그렇다면 BeanFactory와 ApplicationContext는 무엇일까? 이번 글에서 알아보도록 하자.

주석으로 보는 BeanFactory와 ApplicationContext 차이점

BeanFactory ApplicationContext 문서를 보자. 더 자세한 설명은 링크를 통해 보자.

BeanFactory

스프링 빈 컨테이너에 접근하기 위한 최상위 인터페이스라고 설명한다.

문서를 보고 재밌는 사실을 알았는데 IoC Container는 여러 가지 용어로 혼용해서 사용하는데 Spring Container, IoC Container, Bean Container 그리고 스프링 자체를 Container라는 의미로 그냥 스프링이라고 부르기도 하는데 문서에는 정확히 Spring Bean Container 라고 작성되어있다. (약간 잡담)

ApplicationContext

ListableBeanFactory(BeanFactory에 하위 인터페이스이며, Bean을 Listable하게 보관하는 인터페이스를 말한다. 대표적으로 DefaultListableBeanFactory 클래스)를 상속하고 있으며, 여러 기능(ResourceLoader, ApplicationEventPublisher, MessageSource, Bean Lifecycle)을 추가로 제공한다.

  API 문서에는 위와 같이 설명하고 있다. 명확한 차이점을 설명하기 위해서 ResourceLoader, ApplicationEventPublisher, MessageSource, Bean Lifecycle 등, 이러한 인터페이스와 기능이 정확히 어떤 역할을 해야 하는지 설명해야 하지만, 이번 포스팅에서는 그 외에 문서에서 설명하지 않는 또 다른 중요한 차이점을 알아보고자 한다.

이번 글도 마찬가지로 AnnotationConfigApplicationContext를 기준으로 설명하겠다.

코드로 보는 BeanFactory와 ApplicationContext 차이점

AbstractApplicationContext.refresh 메소드

  refresh 메소드는 AnnotationConfigApplicationContext의 부모 클래스인 AbstractApplicationContext에 정의되어 있다. 이 메소드를 설명하기 전에 먼저 refresh 메소드가 어디서 호출이 되는지 알아보고 가자. 사실 bean 등록과정에서 이미 나온 부분이다.

AnnotationConfigApplicationContext 생성

@Test
public void testGetBeanFromJavaConfig() throws Exception {
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
}

AnnotationConfigApplicationContext 객체를 생성한다. 저 생성자 소스를 따라가 보자.

AnnotationConfigApplicationContext(Class... annotatedClassed) 생성자

public AnnotationConfigApplicationContext(Class... annotatedClasses) {
    this();
    this.register(annotatedClasses);
    this.refresh();
}

  바로 여기서 호출된다. 기억이 날것이다. 바로 여기서 refresh 메소드를 호출한다. 그럼 본격적으로 refresh 메소드를 살펴보자.

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        prepareRefresh();

        // Tell the subclass to refresh the internal bean factory.
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);

            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            registerBeanPostProcessors(beanFactory);

            // Initialize message source for this context.
            initMessageSource();

            // Initialize event multicaster for this context.
            initApplicationEventMulticaster();

            // Initialize other special beans in specific context subclasses.
            onRefresh();

            // Check for listener beans and register them.
            registerListeners();

            // Instantiate all remaining (non-lazy-init) singletons.
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            finishRefresh();
        }

        catch (BeansException ex) {
            // 생략...
        }

        finally {
            // 생략...
        }
    }
}

  사실 이 부분에서 여러가지 메소드를 호출하는데, ApplicatoinContext가 제공하는 기능은 모두 이 부분에서 초기화 된다고 보면된다. 하지만 다 설명하긴 어렵고 이번에 설명한 부분은 finishBeanFactoryInitialization 메소드다.

AbstractApplicationContext.finishBeanFactoryInitialization 메소드

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
    // 생략...

    // Instantiate all remaining (non-lazy-init) singletons.
    beanFactory.preInstantiateSingletons();
}

  AbstractApplicationContext가 멤버 변수로 포함하고 있는 beanFactory 클래스에 preinstantiateSingletones 메소드를 호출하고 있다. AbstractApplicationContext가 가지고 있는 beanFactory에 구현체는 DefaultListableBeanFactory이다. 그렇다면 preinstantiateSingletones 메소드는 DefaultListableBeanFactory 에서 찾아보면 될 거 같다.

DefaultListableBeanFactory.preinstantiateSingletones 메소드

@Override
public void preInstantiateSingletons() throws BeansException {
    List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);

    // Trigger initialization of all non-lazy singleton beans...
    for (String beanName : beanNames) {
        RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
        if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
            if (isFactoryBean(beanName)) {
                final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
                boolean isEagerInit;
                if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                    isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
                        @Override
                        public Boolean run() {
                            return ((SmartFactoryBean<?>) factory).isEagerInit();
                        }
                    }, getAccessControlContext());
                }
                else {
                    isEagerInit = (factory instanceof SmartFactoryBean &&
                            ((SmartFactoryBean<?>) factory).isEagerInit());
                }
                if (isEagerInit) {
                    getBean(beanName);
                }
            }
            else {
                getBean(beanName);
            }
        }
    }

    // 생략...
}

  beanDefinitionNames List<String>를 반복문을 통해 beanName으로 getMergedLocalBeanDefinition 메소드를 호출한다. 이 메소드도 기억이난다. 이 메소드가 하는 역할은 기존에 생성되어 저장되었던 GenericBeanDefinion을 RootBeanDefinition으로 재정의한 후 저장 또는 저장후 반환한다. 코드 상에도 return type을 보면 RootBeanDefinition을 반환한다. 그 후 다음 코드 중 getBean을 호출하는 부분이 여러 라인에서 눈에보인다.

getBean 메소드가 하는 역할은 bean instance 생성, 저장, 반환이다. 하지만 instance 생성은 getBean 메소드를 제일 처음 실행할때 생성된다. 그 말은 이 반복문 안에 getBean 메소드를 강제로 호출해 bean instance를 미리 만들어 놓는다는 뜻이다.

이게 바로 제가 생각하는 BeanFactory와 ApplicationContext에 가장 큰 차이점이라고 생각한다.