Spring IoC Container를 까보자 #Bean을 어떻게 반환할까?

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

Spring IoC Container를 까보자 #Bean 등록은 어떻게 될까? 라는 글에서 IoC Container인 BeanFactory와 ApplicationContext에서 어떻게 Bean을 등록하고 보관하는지 알아보았다. 이번 글에서는 등록된 Bean 정확히 말하면 BeanDefinition을 어떻게 instance 화 후 반환 하는지 알아보자.

getBean 메소드의 정체

@Configuration
public class BeanConfig {

    @Bean
    public AccountService accountService() {
        AccountService accountService = new AccountService();
        accountService.setAccountRepository(accountRepository());
        return accountService;
    }

    @Bean
    public AccountRepository accountRepository() {
        return new AccountRepository();
    }
}

  Bean은 어떻게 등록될까?에서 설명한 메타데이터 역할인 BeanConfig다. 다시 한번 보자. AccountService와 AccountRepository라는 Bean을 등록했다고 가정하자. (기능은 생각하지 말자. 우린 Bean이 어떻게 반환되는지만 확인하면 된다.)

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
AccountService accountService = beanFactory.getBean(AccountService.class);
AccountRepository accountRepository = beanFactory.getBean(AccountRepository.class);

  앞서 살펴본 AnnotationConfigApplicationContext 생성 후 getBean(Class requiredType); 메소드를 호출했다. 이 메소드 호출 시 어떤 일이 일어날까? 다이어그램을 통해 구조를 다시 한번 보자.


  처음 다이어그램을 봤을 땐 클래스와 인터페이스들이 하는 역할이 도통 무슨 책임을 가졌는지 확인하기 어려웠다. 아마 지금은 약간 익숙해졌을 거다.

  getBean 메소드는 BeanFactory 인터페이스에 선언되어있다. 우리가 생성한 AnnotationConfigApplicationContext 클래스에 getBean 메소드는 어디에 어떻게 구현되어있는지 확인해보자.


  intellij에서 AnnotationConfigApplicationConfig 클래스에서 getBean 메소드를 검색해보았다. 부모 클래스인 AbstractApplicationContext에 구현되어있다.

@Override
public <T> T getBean(Class<T> requiredType) throws BeansException {
    assertBeanFactoryActive();
    return getBeanFactory().getBean(requiredType);
}

AbstractApplicationContext.getBean 메소드 코드다. return 부분을 보면 getBeanFactory 메소드 호출 후 getBean 메소드를 호출하는 것을 볼 수 있다.

@Override
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

  AbstractApplicationContext.getBeanFactory 메소드는 추상 메소드다. 그럼 AbstractApplicationContext를 상속한 자식인 AnnotationConfigApplication 또는 GenericApplicationContext에 getBeanFactory 메소드 구현 코드를 찾아보자.

@Override
public final ConfigurableListableBeanFactory getBeanFactory() {
    return this.beanFactory;
}

  이전 글을 정확히 이해했다면 어느 정도 눈치는 챘을 것이다. getBeanFactory 메소드를 구현한 구현체는 GenericApplicationContext 클래스다. 기억나는가? GenericApplicationContext는 BeanFactory와 ApplicationContext에 구현체인 동시에 DefaultListableBeanFactory를 멤버변수로 선언하며 BeanFactory 구현체로 사용하고있다.

getBean 메소드는 결국 DefaultListableBeanFactory에서 호출된다. 즉, 실제 getBean을 구현한 코드는 ApplicatioinContext 구현체들과는 무관하다는 것을 알수 있다. 실제 구현 코드는 모두 BeanFactory 구현체들이 담당하며 ApplicationContext는 BeanFactory 구현체(이 글에서는 DefaultListableBeanFactory)를 사용할 뿐이다.

Bean instance도 DefaultListableBeanFactory가 만들어 낼까?

결론부터 말하면 그렇지 않다. 말보다는 코드로 확인해보자.

@Override
public <T> T getBean(Class<T> requiredType, Object... args) throws BeansException {
    NamedBeanHolder<T> namedBean = resolveNamedBean(requiredType, args);
    if (namedBean != null) {
        return namedBean.getBeanInstance();
    }
    BeanFactory parent = getParentBeanFactory();
    if (parent != null) {
        return parent.getBean(requiredType, args);
    }
    throw new NoSuchBeanDefinitionException(requiredType);
}

  위 코드는 DefaultListableBeanFactory.getBean(Class requiredType) 메소드를 따라가 보면 overiding 메소드다. NamedBeanHolder 클래스를 만들어 getBeanInstance 메소드를 반환한다. 분명한건 이 부분에서 instance를 반환하는것인데 어떻게 만드는지 확인해보자.

DefaultListableBeanFactory.resolveNamedBean 메소드

private <T> NamedBeanHolder<T> resolveNamedBean(Class<T> requiredType, Object... args) throws BeansException {
    Assert.notNull(requiredType, "Required type must not be null");
    String[] candidateNames = getBeanNamesForType(requiredType);

    // 생략...

    if (candidateNames.length == 1) {
        String beanName = candidateNames[0];
        return new NamedBeanHolder<T>(beanName, getBean(beanName, requiredType, args));
    }
    // 생략...
}

  NamedBeanHolder를 만드는 resolveNamedBean 메소드다. 다른 부분은 생략했다. 반환하는 부분을 보자. NamedBeanHolder를 생성하면서 생성자 파라미터에 getBean 메소드를 호출하는 것을 볼 수 있다. 따라가보자.

AbstractBeanFactory.getBean(String name, Class requiredType, Object... args) 메소드

protected <T> T doGetBean(
        final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
        throws BeansException {

    final String beanName = transformedBeanName(name);
    Object bean;

    // Eagerly check singleton cache for manually registered singletons.
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
        if (logger.isDebugEnabled()) {
            if (isSingletonCurrentlyInCreation(beanName)) {
                logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
                        "' that is not fully initialized yet - a consequence of a circular reference");
            }
            else {
                logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
            }
        }
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }

    else {
        final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
    
        // 생략...

        try {
        
            // 생략...

            // Create bean instance.
            if (mbd.isSingleton()) {
                sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
                    @Override
                    public Object getObject() throws BeansException {
                        try {
                            return createBean(beanName, mbd, args);
                        }
                        catch (BeansException ex) {
                            destroySingleton(beanName);
                            throw ex;
                        }
                    }
                });
                bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
            }

            else if (mbd.isPrototype()) {
                // It's a prototype -> create a new instance.
                Object prototypeInstance = null;
                try {
                    beforePrototypeCreation(beanName);
                    prototypeInstance = createBean(beanName, mbd, args);
                }
                finally {
                    afterPrototypeCreation(beanName);
                }
                bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
            }

            else {
                String scopeName = mbd.getScope();
                final Scope scope = this.scopes.get(scopeName);
                if (scope == null) {
                    throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
                }
                try {
                    Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
                        @Override
                        public Object getObject() throws BeansException {
                            beforePrototypeCreation(beanName);
                            try {
                                return createBean(beanName, mbd, args);
                            }
                            finally {
                                afterPrototypeCreation(beanName);
                            }
                        }
                    });
                    bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
                }
                catch (IllegalStateException ex) {
                    throw new BeanCreationException(beanName,
                            "Scope '" + scopeName + "' is not active for the current thread; consider " +
                            "defining a scoped proxy for this bean if you intend to refer to it from a singleton",
                            ex);
                }
            }
        }
        catch (BeansException ex) {
            cleanupAfterBeanCreationFailure(beanName);
            throw ex;
        }
    }

    // 생략...
    
    return (T) bean;
}

getBean 메소드는 결국 부모 클래스인 AbstractBeanFactory.doGetBean 메소드를 호출한다. 

  • getSingleton 메소드를 통해 object 를 생성한다. 
  • getObjectForBeanInstance 메소드를 통해 bean을 생성해 반환한다.
  • scope를 구분해 instance를 생성하는 부분이 바로 doGetBean 메소드에서 수행하는 것을 확인했다.

  doGetBean 메소드에서 bean instance를 생성/반환하는데 주 역할을 하는 getSingleton 메소드와 getObjectForBeanInstance 메소드를 살펴보자.

DefaultSingletonBeanRegistry.getSingleton 메소드

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

  DefaultListableBeanFactory가 아닌 DefaultSingletonBeanRegistry 클래스 메소드다. earlySingletoneObjects Map<String, Object>에 singletonObject를 가져(get)오거나, 저장(put)한다. 

DefaultSingletonBeanRegistry는 AbstractBeanFactory에 부모 클래스다.


AbstractBeanFactory.getObjectForBeanInstance 메소드

protected Object getObjectForBeanInstance(
        Object beanInstance, String name, String beanName, RootBeanDefinition mbd) {

    // Don't let calling code try to dereference the factory if the bean isn't a factory.
    if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) {
        throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
    }

    // Now we have the bean instance, which may be a normal bean or a FactoryBean.
    // If it's a FactoryBean, we use it to create a bean instance, unless the
    // caller actually wants a reference to the factory.
    if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
        return beanInstance;
    }

    Object object = null;
    if (mbd == null) {
        object = getCachedObjectForFactoryBean(beanName);
    }
    if (object == null) {
        // Return bean instance from factory.
        FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
        // Caches object obtained from FactoryBean if it is a singleton.
        if (mbd == null && containsBeanDefinition(beanName)) {
            mbd = getMergedLocalBeanDefinition(beanName);
        }
        boolean synthetic = (mbd != null && mbd.isSynthetic());
        object = getObjectFromFactoryBean(factory, beanName, !synthetic);
    }
    return object;
}
  • getCachedObjectForFactoryBean : 저장되어 있는 bean instance 반환
  • getMergedLocalBeanDefinition : RootBeanDefinition을 생성/반환
  • getObjectFromFactoryBean : bean instance 생성/반환

위 3가지 메소드를 보자.

FactoryBeanRegistrySupport.getCachedObjectForFactoryBean 메소드

protected Object getCachedObjectForFactoryBean(String beanName) {
    Object object = this.factoryBeanObjectCache.get(beanName);
    return (object != NULL_OBJECT ? object : null);
}

  factoryBeanObjectCache Map<String, Object>에서 object를 꺼내 반환한다.

getBean 메소드 호출이 처음이 아니라면 이미 bean instance는 저장되어 있기 때문에 이 메소드에서 bean instance를 반환한다.(뒤에서 자세히 설명)

AbstractBeanFactory.getMergedLocalBeanDefinition 메소드

protected RootBeanDefinition getMergedBeanDefinition(
        String beanName, BeanDefinition bd, BeanDefinition containingBd)
        throws BeanDefinitionStoreException {

    synchronized (this.mergedBeanDefinitions) {
        RootBeanDefinition mbd = null;

        // Check with full lock now in order to enforce the same merged instance.
        if (containingBd == null) {
            mbd = this.mergedBeanDefinitions.get(beanName);
        }

        if (mbd == null) {
            // 생략...

            // Only cache the merged bean definition if we're already about to create an
            // instance of the bean, or at least have already created an instance before.
            if (containingBd == null && isCacheBeanMetadata()) {
                this.mergedBeanDefinitions.put(beanName, mbd);
            }
        }

        return mbd;
    }
}

  getMergedLocalBeanDefinition 메소드에서는 mergedBeanDefinitions Map<String, RootBeanDefinition>에서 RootBeanDefinition(BeanDefinition)을 가져(get)오거나, 저장(put)한다.

RootBeanDefinition은 bean 등록 시 생성 되었던 GenericBeanDefinition과 그외 데이터(BeanDefinitionHolder, AnnotatedElement, 등)를 재 정의한 클래스.

FactoryBeanRegistrySupport.getObjectFromFactoryBean 메소드

protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
    if (factory.isSingleton() && containsSingleton(beanName)) {
        synchronized (getSingletonMutex()) {
            Object object = this.factoryBeanObjectCache.get(beanName);
            if (object == null) {
                object = doGetObjectFromFactoryBean(factory, beanName);
                Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
                if (alreadyThere != null) {
                    object = alreadyThere;
                }
                else {
                    if (object != null && shouldPostProcess) {
                        try {
                            object = postProcessObjectFromFactoryBean(object, beanName);
                        }
                        catch (Throwable ex) {
                            throw new BeanCreationException(beanName,
                                    "Post-processing of FactoryBean's singleton object failed", ex);
                        }
                    }
                    this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT));
                }
            }
            return (object != NULL_OBJECT ? object : null);
        }
    }
    else {
        // 생략...
    }
}

  factoryBeanObjectCache Map<String, Object>에서 bean instance를 가져(get)오거나, 저장(put)한다. 
  isSingleton 메소드를 통해 bean이 singleton인지 확인한다. 그렇지 않은 경우(다른 Scope)인 경우도 이 메소드에서 처리하는 걸 알 수 있다. (이 부분은 설명 생략)

  factoryBeanObjectCache Map에서는 bean instance를 저장하고 mergedBeanDefinitions Map에서는 RootBeanDefinition을 저장하여 관리하고 있다.

즉, 최초에 getBean 메소드 호출 시 bean instance와 RootBeanDefinition을 생성해 Map에 저장 후 instance를 반환한다. 그 후 getBean 메소드 호출 시 Map에 저장되어 있는 걸 그대로 꺼내 반환한다.(singleton 인 경우에 한에.)

요약

코드 depth가 너무 깊어 코드를 이해하기 어렵고, 나로써는 설명하기도 어렵다. 잘 설명이 됐는지는 모르겠지만 다시 한번 요약해서 정리해보자.

  1. 메타데이터로 Bean을 BeanDefinition 클래스로 정의 후 저장한다. (Spring IoC Container를 까보자 #Bean 등록은 어떻게 될까?)
  2. getBean 메소드 호출 시 bean에 scope를 구분하여 scope에 맞게 객체를 생성 또는 반환한다. (이 글에서는 singleton 기준으로 설명했다.)
  3. 처음 getBean 메소드를 호출 할 경우 처음 등록 시 저장되었던 GenericBeanDefinition을 통해 RootBeanDefinition을 생성하여 저장한다. (더 많은 Bean에 데이터 정의와 BeanDefinition 통합)
  4. 그리고 bean instance를 생성 후 Map에 저장 한 후 그대로 반환한다.
  5. 2번 이상의 getBean 메소드 호출인 경우에는 Map에 저장되어 있는 bean instance를 반환한다.

getBean 메소드는 내부적으로 2가지 역할을 한다. bean instance와 RootBeanDefinition 생성 후 저장 그리고 bean instance 반환

출처 : https://blog.woniper.net/337?category=699184