BeanFactory와 ApplicationContext 인터페이스

사용자 삽입 이미지IoC의 가장 상위에 있는 인터페이스인 BeanFactory에 있는 메소드들 입니다.

사용자 삽입 이미지ApplicationContext 인터페이스에 있는 메소드 들인데 이 인터페이스는 아래의 인터페이스를 확장하고 있습니다.
ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver
위 인터페이스들의 클래스 다이어그램을 그려보면 다음과 같이 됩니다.

사용자 삽입 이미지
BeanFactory를 구현한 클래스들(ApplicationContext를 구현한 클래스와 중복되는 클래스는 삭제했습니다.)

AbstractBeanFactory, DefaultListableBeanFactory, StaticListableBeanFactory, XmlBeanFactory,

ApplicationContext를 구현한 클래스들

AbstractApplicationContext, AbstractRefreshableApplicationContext, AbstractRefreshablePortletApplicationContext, AbstractRefreshableWebApplicationContext, AbstractXmlApplicationContext, ClassPathXmlApplicationContext, FileSystemXmlApplicationContext, GenericApplicationContext, GenericWebApplicationContext, StaticApplicationContext, StaticPortletApplicationContext, StaticWebApplicationContext, XmlPortletApplicationContext, XmlWebApplicationContext

이 많은 IoC container들 중에 언제 어떤 걸 써야 할런지….;;;;

참조 : BeanFactory 이해

Object getBean(String name) 메소드 구현 살펴보기-윗 부분

Object getBean(String name) throwsBeansException 메소드는 name에 해당하는bean의 객체를 반환합니다. 이 메소드는BeanFactory Singleton 또는Prototype 패턴을 사용하는 것을 보여줍니다. name으로 bean의 별명을 넘겨 줘도 그에 상응하는 bean 객체를 받아 볼수 있으며 이 경우에도 역시 해당하는 name이 없다면 상위BeanFactory에서 찾게 됩니다. 이 메소드는 특히RuntimeException을 발생 시킬 수 있는데 BeanException bean 객체를 가져오지 못했을 때 발생하며 BeanException의하위 클래스인 NoSuchBeanDefinitionException은 해당하는 name bean을 찾지 못했을 때 발생합니다.

먼저 소스코드를 보겠습니다.

public Object getBean(String name) throws BeansException {
       return getBean(name, null, null);
   }

1231377222.bmp
아.. 다른 메소드를 호출 하고 있습니다. 혹시 getBean(String name, Class requiredType) 메소드도 이러한 모습을 하고 있지 않을까 예상됩니다.

public Object getBean(String name, Class requiredType) throws BeansException {
       return getBean(name, requiredType, null);
   }

1022700860.bmp
역시 같은 모습을 하고 있었습니다.

이번 글에서는 두 개의 메소드 구현을 한꺼번에 살펴볼 수 있게 됐습니다. 🙂 이거양득?

먼저 세개의 아규먼트들이 무엇에 사용되는지 주석을 보겠습니다.

/**
    * Return the bean with the given name,
    * checking the parent bean factory if not found.
    * @param name the name of the bean to retrieve
    * @param requiredType the required type of the bean to retrieve
    * @param args arguments to use if creating a prototype using explicit arguments to a
    * static factory method. It is invalid to use a non-null args value in any other case.
    */

name과 class는 알겠는데 args 라는 final Object[] 타입이 무엇인지 잘 모르겠네요. 정적 팩토리 메소드를 사용해서prototype을 생성할 때 사용할 아규먼트들이라고 합니다.

Object getBean(String name, Class requiredType, final Object[] args) 메소드 보기

[#M_ more.. | less.. |
public Object getBean(String name, Class requiredType, final Object[] args) throws BeansException {
       final String beanName = transformedBeanName(name);
       Object bean = null;

       // Eagerly check singleton cache for manually registered singletons.
       Object sharedInstance = getSingleton(beanName);
       if (sharedInstance != null) {
           if (isSingletonCurrentlyInCreation(beanName)) {
               if (logger.isDebugEnabled()) {
                   logger.debug(“Returning eagerly cached instance of singleton bean ‘” + beanName +
                           “‘ that is not fully initialized yet – a consequence of a circular reference”);
               }
           }
           else {
               if (logger.isDebugEnabled()) {
                   logger.debug(“Returning cached instance of singleton bean ‘” + beanName + “‘”);
               }
           }
           if (containsBeanDefinition(beanName)) {
               RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition(beanName, false);
               bean = getObjectForBeanInstance(sharedInstance, name, mergedBeanDefinition);
           }
           else {
               bean = getObjectForBeanInstance(sharedInstance, name, null);
           }
       }

       else {
           // Fail if we’re already creating this singleton instance:
           // We’re assumably within a circular reference.
           if (isSingletonCurrentlyInCreation(beanName)) {
               throw new BeanCurrentlyInCreationException(beanName);
           }

           // Check if bean definition exists in this factory.
           BeanFactory parentBeanFactory = getParentBeanFactory();
           if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
               // Not found -> check parent.
               String nameToLookup = originalBeanName(name);
               if (parentBeanFactory instanceof AbstractBeanFactory) {
                   // Delegation to parent with args only possible for AbstractBeanFactory.
                   return ((AbstractBeanFactory) parentBeanFactory).getBean(nameToLookup, requiredType, args);
               }
               else if (args == null) {
                   // No args -> delegate to standard getBean method.
                   return parentBeanFactory.getBean(nameToLookup, requiredType);
               }
               else {
                   throw new NoSuchBeanDefinitionException(beanName,
                           “Cannot delegate to parent BeanFactory because it does not supported passed-in arguments”);
               }
           }

           this.alreadyCreated.add(beanName);

           final RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition(beanName, false);
           checkMergedBeanDefinition(mergedBeanDefinition, beanName, args);

           // Create bean instance.
           if (mergedBeanDefinition.isSingleton()) {
               sharedInstance = getSingleton(beanName, new ObjectFactory() {
                   public Object getObject() throws BeansException {
                       try {
                           return createBean(beanName, mergedBeanDefinition, args);
                       }
                       catch (BeansException ex) {
                           // Explicitly remove instance from singleton cache: It might have been put there
                           // eagerly by the creation process, to allow for circular reference resolution.
                           // Also remove any beans that received a temporary reference to the bean.
                           destroySingleton(beanName);
                           throw ex;
                       }
                   }
               });
               bean = getObjectForBeanInstance(sharedInstance, name, mergedBeanDefinition);
           }

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

           else {
               String scopeName = mergedBeanDefinition.getScope();
               final Scope scope = (Scope) this.scopes.get(scopeName);
               if (scope == null) {
                   throw new IllegalStateException(“No Scope registered for scope ‘” + scopeName + “‘”);
               }
               try {
                   Object scopedInstance = scope.get(beanName, new ObjectFactory() {
                       public Object getObject() throws BeansException {
                           beforePrototypeCreation(beanName);
                           try {
                               Object bean = createBean(beanName, mergedBeanDefinition, args);
                               if (requiresDestruction(bean, mergedBeanDefinition)) {
                                   scope.registerDestructionCallback(beanName,
                                           new DisposableBeanAdapter(bean, beanName, mergedBeanDefinition, getBeanPostProcessors()));
                               }
                               return bean;
                           }
                           finally {
                               afterPrototypeCreation(beanName);
                           }
                       }
                   });
                   bean = getObjectForBeanInstance(scopedInstance, name, mergedBeanDefinition);
               }
               catch (IllegalStateException ex) {
                   throw new BeanCreationException(beanName, “Scope ‘” + scopeName + “‘ is not active”, ex);
               }
           }
       }

       // Check if required type matches the type of the actual bean instance.
       if (requiredType != null && !requiredType.isAssignableFrom(bean.getClass())) {
           throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
       }
       return bean;
   }
_M#]
헉…. 깁니다….

메소드를 가장 밖에 있는 if 문을 기준으로 삼등분 시키겠습니다.


[#M_ more.. | less.. |
public Object getBean(String name, Class requiredType, final Object[] args) throws BeansException {
       final String beanName = transformedBeanName(name);
       Object bean = null;

       // Eagerly check singleton cache for manually registered singletons.
       Object sharedInstance = getSingleton(beanName);
     

if (sharedInstance != null) {
           

if (isSingletonCurrentlyInCreation(beanName)) {
    if (logger.isDebugEnabled()) {
        logger.debug(“Returning eagerly cached instance of singleton bean ‘” + beanName + “‘ that is not fully initialized yet – a consequence of a circular reference”);
     }
} else {
    if (logger.isDebugEnabled()) {
        logger.debug(“Returning cached instance of singleton bean ‘” + beanName + “‘”);
    }
}

         

if (containsBeanDefinition(beanName)) {
     RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition(beanName, false);
     bean = getObjectForBeanInstance(sharedInstance, name, mergedBeanDefinition);
  } else {
     bean = getObjectForBeanInstance(sharedInstance, name, null);
  }

}

_M#]
세 부분으로 나누니까 그래도 조금 안심이 되네요. 하지만 여전히 중간 부분이 굉장히 크네요. 먼저 제일 윗 부분 부터 살펴보겠습니다.

이 부분은 getSingleton(String) 메소드를 호출한 결과 값이 null이 아닐 경우에 실행되는 부분입니다. 그리고 크게 두 부분으로 나뉘어 지는데 첫번째 부분은 메소드 이름으로 추정해 보건데 싱글톤 객체가 현재 생성 중일 때 또는 그렇지 않을 때 logging 메시지를 처리하는 부분이라고 생각됩니다.

다음 부분은 실질적으로 빈을 생성하는 부분인듯 합니다. 빈 이름에 해당하는 빈 정의가 존재할 때와 그렇치 않을 때 각각 getObjectForBeanInstance()를 통해 bean을 받아 오고 있습니다.

이전에도 몇 번 본적이 있는 transformedBeanName(String) 메소드입니다.
[#M_ more.. | less.. |
protected String transformedBeanName(String name) {
       String beanName = BeanFactoryUtils.transformedBeanName(name);
       // Handle aliasing.
       synchronized (this.aliasMap) {
           String canonicalName = (String) this.aliasMap.get(beanName);
           return (canonicalName != null ? canonicalName : beanName);
       }
   }
_M#]
Object sharedInstance = getSingleton(beanName); 여기서 호출되는 getSingleton(String)메소드 입니다. 이 메소드는 DefaultSingletonBeanRegistry 클래스에 있습니다. DefaultSingletonBeanRegistry 클래스와 AbstractBeanFactory와의 관계는 바로 다른 글에서 언급이 되었습니다. 짧게 D가 A의 아버지 입니다.
[#M_ more.. | less.. |
//DefaultSingletonBeanRegistry class

public Object getSingleton(String beanName) {
       synchronized (this.singletonCache) {
           return this.singletonCache.get(beanName);
       }
   }
_M#]
logging 관련 부분은 나중에 살펴보도록 하고 getMergedBeanDefinition(String, boolean) 메소드를 보겠습니다.
[#M_ more.. | less.. |
/**
    * Return a RootBeanDefinition, even by traversing parent if the parameter is a
    * child definition. Can ask the parent bean factory if not found in this instance.
    * @param name the name of the bean to retrieve the merged definition for
    * @param includingAncestors whether to ask the parent bean factory if not found
    * in this instance
    * @return a (potentially merged) RootBeanDefinition for the given bean
    * @throws NoSuchBeanDefinitionException if there is no bean with the given name
    * @throws BeanDefinitionStoreException in case of an invalid bean definition
    */
   protected RootBeanDefinition getMergedBeanDefinition(String name, boolean includingAncestors)
       throws BeansException {

       String beanName = transformedBeanName(name);

       // Efficiently check whether bean definition exists in this factory.
       if (includingAncestors && !containsBeanDefinition(beanName) &&
               getParentBeanFactory() instanceof AbstractBeanFactory) {
           return ((AbstractBeanFactory) getParentBeanFactory()).getMergedBeanDefinition(beanName, true);
       }

       // Resolve merged bean definition locally.
       return getMergedBeanDefinition(beanName, getBeanDefinition(beanName));
   }
_M#]
마지막으로 getObjectForBeanInstance(Object, String,  RootBeanDifinition) 메소드를 살펴보겠습니다.
[#M_ more.. | less.. |
/**
    * Get the object for the given bean instance, either the bean
    * instance itself or its created object in case of a FactoryBean.
    * @param beanInstance the shared bean instance
    * @param name name that may include factory dereference prefix
    * @param mbd the merged bean definition
    * @return the object to expose for the bean
    */
protected Object getObjectForBeanInstance(Object beanInstance, String name, RootBeanDefinition mbd) throws BeansException {

       String beanName = transformedBeanName(name);

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

       boolean shared = (mbd == null || mbd.isSingleton());
       Object object = beanInstance;

       // 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) {
           if (!BeanFactoryUtils.isFactoryDereference(name)) {
               // Return bean instance from factory.
               FactoryBean factory = (FactoryBean) beanInstance;
               if (logger.isDebugEnabled()) {
                   logger.debug(“Bean with name ‘” + beanName + “‘ is a factory bean”);
               }
               // Cache object obtained from FactoryBean if it is a singleton.
               if (shared && factory.isSingleton()) {
                   synchronized (this.factoryBeanObjectCache) {
                       object = this.factoryBeanObjectCache.get(beanName);
                       if (object == null) {
                           object = getObjectFromFactoryBean(factory, beanName, mbd);
                           this.factoryBeanObjectCache.put(beanName, object);
                       }
                   }
               }
               else {
                   object = getObjectFromFactoryBean(factory, beanName, mbd);
               }
           }
           else {
                // The user wants the factory itself.
               if (logger.isDebugEnabled()) {
                   logger.debug(“Calling code asked for FactoryBean instance for name ‘” + beanName + “‘”);
               }
           }
       }

       return object;
   }
_M#]
getObjectForBeanInstance 메소드는 좀 더 자세히 살펴 봐야겠습니다.

BeanFactory와 AbstractBeanFactory의 관계

1301622120.bmp
AbstractBeanFactory는 총 네 개의 인터페이스 구현에 대한 책임이 있지만 abstract class인 관계로 몇몇 메소드는 abstract인 채로 하위 클래스에서 구현하도록 되있습니다.

그리고 네 개의 인터페이스 중에 하나 인 SingletonBeanRegistry 인터페이스는 DefaultSingletonBeanRegistry 클래스에서 구현을 했고 이 클래스를 상속 받고 있는 구조입니다.

UML에 차마 다 그리기가 힘들어서 ConfigurableBeanFactory의 메소드들과 AbstractBeanFactory의 필드와 메소드들을 비워놨습니다.

현재 BeanFactory 인터페이스, HierachicalBeanFactory 인터페이스, SingletonBeanRegistry 인터페이스 만 모두 표현해 둔 상태입니다.

mission1의 문제는 Xml을 읽어들여 BeanFactory가 작동하는 방식을 규명하는 것인데..

결국 XmlBeanFactoy까지 내려 가야 할텐데 지금은 AbstractBeanFactory에서 BeanFactory 인터페이스의 책임들을 어떻게 구현해 두었는지 살펴보고 있습니다.

ps : getBean 메소드 무지 길어요. 이거 리팩토링 해야되는거 아니에요? ^^

String[] getAliases(String name) 메소드 구현 살펴보기

BeanFactory 인터페이스에 있는 메소드들을 어떻게 구현했나 살펴보고 있습니다.

[#M_ more.. | less.. |
public String[] getAliases(String name) {
       String beanName = transformedBeanName(name);
       List aliases = new ArrayList();
       boolean factoryPrefix = name.startsWith(FACTORY_BEAN_PREFIX);
       String fullBeanName = beanName;
       if (factoryPrefix) {
           fullBeanName = FACTORY_BEAN_PREFIX + beanName;
       }
       if (!fullBeanName.equals(name)) {
           aliases.add(fullBeanName);
       }
       synchronized (this.aliasMap) {
           for (Iterator it = this.aliasMap.entrySet().iterator(); it.hasNext();) {
               Map.Entry entry = (Map.Entry) it.next();
               if (entry.getValue().equals(beanName)) {
                   String key = (factoryPrefix ? FACTORY_BEAN_PREFIX : “”) + entry.getKey();
                   if (!key.equals(name)) {
                       aliases.add(key);
                   }
               }
           }
       }
       if (!containsSingleton(beanName) && !containsBeanDefinition(beanName)) {
           BeanFactory parentBeanFactory = getParentBeanFactory();
           if (parentBeanFactory != null) {
               aliases.addAll(Arrays.asList(parentBeanFactory.getAliases(fullBeanName)));
           }
       }
       return StringUtils.toStringArray(aliases);
   }
_M#]
if (!fullBeanName.equals(name)) {
           aliases.add(fullBeanName);
       }
저는 이부분이 이해가 잘 안가네요.

name은 매개변수로 받아온 이름이고 만약에 그 name이 &로 시작을 하면 fullBeanName은 transformedBeanName(String) 메소드 호출하여 반환 된 값을 참조하는 beanName에 &를 붙인 값이 됩니다.

그런데 transformedBeanName(String) 메소드는 아래에 보시다시피 &로 시작하는 name이 오면 맨 앞에 &를 제외한 상태로 문자열을 반환하게 됩니다.

transformedBeanName(String) 메소드

[#M_ more.. | less.. |
protected String transformedBeanName(String name) {
       String beanName = BeanFactoryUtils.transformedBeanName(name);
       // Handle aliasing.
       synchronized (this.aliasMap) {
           String canonicalName = (String) this.aliasMap.get(beanName);
           return (canonicalName != null ? canonicalName : beanName);
       }
   }

//BeanFactoryUtils class

public static String transformedBeanName(String name) {
       Assert.notNull(name, “Name must not be null”);
       String beanName = name;
       if (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
           beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
       }
       return beanName;
   }
_M#]
그렇게 때문에 if (!fullBeanName.equals(name)) 여기서 어떻게 name하고 fullName하고 달라질 수가 있는지.. 모르겠네요.

이 메소드에서 호출되는 다른 메소드 들도 containsBean(String) 메소드에서 확인바 있습니다.

ps : 이 메소드에서도 역시 제가 아직 풀지 못한 수수께끼.. FACTORY_BEAN_PREFIX  이 변수와 관련해서 name으로 받아온 String의 제일 앞 글자가 &로 시작하면 무슨일이 벌어지는 걸까요…

boolean containsBean(String name) 메소드 구현 살펴보기.

BeanFactory 인터페이스에 있는 메소드의 구현을 살펴보겠습니다.

String[] getAliases(String name) 메소드는 name으로 참조 되는 bean의 다른 이름들 즉 별명들을 String 배열로 반환하는 메소드 입니다. name으로 넘겨진 값이별명 중에 하나였다면 그에 상응하는 bean의 원래 name과별명들을 같이 반환합니다. 이때 원래 name은 배열의 제일처음에 위치합니다. 이 메소드 또한 현재 BeanFactory에서해당하는 bean을 찾지 못한다면 부모 BeanFactory에서찾게 됩니다.

Best 시나리오 일때는 자기 자신의 favtory안에서 String type의 매개변수에 해당하는 bean을 찾을 겨우 입니다. 이때는 다음과 같은 메소드 호출 Stack으로 파악할 수 있습니다.

1140146764.bmp
AbstractBeanFactory에 구현된 boolean containsBean(String)

[#M_ more.. | less.. |
public boolean containsBean(String name) {
       if (containsLocalBean(name)) {
           return true;
       }
       // Not found -> check parent.
       BeanFactory parentBeanFactory = getParentBeanFactory();
       if (parentBeanFactory != null) {
           return parentBeanFactory.containsBean(originalBeanName(name));
       }
       return false;
   }
_M#]

containsLocalBean(String) 메소드

[#M_ more.. | less.. |
public boolean containsLocalBean(String name) {
       String beanName = transformedBeanName(name);
       return (containsSingleton(beanName) || containsBeanDefinition(beanName));
   }_M#]

containsSingleton(String) 메소드 와 containsBeanDefinition(String) 메소드

[#M_ more.. | less.. |
public boolean containsSingleton(String beanName) {
       Assert.hasText(beanName, “Bean name must not be empty”);
       synchronized (this.singletonCache) {
           return this.singletonCache.containsKey(beanName);
       }
   }

protected abstract boolean containsBeanDefinition(String beanName);
_M#]
자기 자시의 BeanFactory에서 찾지 못할 때는 부모 BeanFactory에서 찾게 됩니다. 아래는 그림은 containsBean 메소드의 구조를 도식화 한것입니다.

1365303199.bmp
containsBean 메소드에 넘어온 매개변수를 originalBeanName 메소드의 인자로 전해주고(파란선) 그 결과 값을 다시 parentBeanFacory.containsBean(String) 메소드의 인자로 넘겨줍니다.(빨간선)
parentBeanFactory.containsBean(String) 이부분을 Stack 구조로 나타내면 다음과 같습니다.

1039256663.bmp
getParentBeanFactory() 메소드

[#M_ more.. | less.. |
public BeanFactory getParentBeanFactory() {
       return parentBeanFactory;
   }
_M#]

originalBeanName(String) 메소드

[#M_ more.. | less.. |
protected String originalBeanName(String name) {
       String beanName = transformedBeanName(name);
       if (name.startsWith(FACTORY_BEAN_PREFIX)) {
           beanName = FACTORY_BEAN_PREFIX + beanName;
       }
       return beanName;
   }
_M#]


AbstractBeanFactory transformedBeanName(String) 메소드와BeanFactoryUtils에 있는 transformedBeanName(String) 메소드

[#M_ more.. | less.. |
protected String transformedBeanName(String name) {
       String beanName = BeanFactoryUtils.transformedBeanName(name);
       // Handle aliasing.
       synchronized (this.aliasMap) {
           String canonicalName = (String) this.aliasMap.get(beanName);
           return (canonicalName != null ? canonicalName : beanName);
       }
   }

//BeanFactoryUtils class

public static String transformedBeanName(String name) {
       Assert.notNull(name, “Name must not be null”);
       String beanName = name;
       if (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
           beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
       }
       return beanName;
   }
_M#]

ps : BeanFactory 인터페이스를 구현한 AstractBeanFactory에 있는 method들을 살펴보았습니다.
앞서 궁금해 했던 BeanFactory.FACTORY_BEAN_PREFIX 이 필드를 사용하는 것을 볼 수 있는데요. &를 생성하고자 하는 Bean name앞에 붙여서 getBean을 하게 되면 어떻게 되는건지 해봐야겠네요.

ps : 오타가 많은 듯 한데 찾기가 쉽지 않군요. ㅎㅎ;;;