BeanFactoryAware 테스트

    @Test
    public void testBeanFactoryAware() {
        BeanFactory beanFactory = bean.getBeanFactory();
        assertNotNull(beanFactory);
    }

테스트 코드는 간단합니다. 이전 글에 사용했던 클레스에 BeanFactoryAware 인터페이스를 추가로 구현합니다.

public class BeanLifeCycleTestBean implements BeanFactoryAware, … {

    … 생략

    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println(“setBeanFactory() 실행합니다.”);
        this.beanFactory = beanFactory;
    }

    public BeanFactory getBeanFactory() {
        return beanFactory;
    }

}

테스트를 실행하면 테스트가 통과 하는 걸 확인 할 수 있습니다. 그리고 Output 창을 통해 확인 하면 다음과 같습니다.

setBeanName() 실행합니다.
setBeanClassLoader() 실행합니다.
setBeanFactory() 실행합니다.

BeanFactoryAware 사용 예
ObjectFactoryCreatingFactoryBean 사용 예

이 인터페이스를 구현한 클레스들..

AbstractAdvisorAutoProxyCreator, AbstractAutoProxyCreator, AbstractBeanFactoryBasedTargetSource, AbstractBeanFactoryBasedTargetSourceCreator, AbstractBeanFactoryPointcutAdvisor, AbstractFactoryBean, AbstractPoolingTargetSource, AbstractPrototypeBasedTargetSource, AnnotationAwareAspectJAutoProxyCreator, AspectJAwareAdvisorAutoProxyCreator, BeanConfigurerSupport, BeanFactoryDataSourceLookup, BeanNameAutoProxyCreator, BeanReferenceFactoryBean, CommonsPoolTargetSource, DefaultAdvisorAutoProxyCreator, DefaultBeanFactoryPointcutAdvisor, HibernateAccessor, HibernateAccessor, HibernateInterceptor, HibernateInterceptor, HibernateTemplate, HibernateTemplate, HibernateTransactionManager, HibernateTransactionManager, LazyInitTargetSource, LazyInitTargetSourceCreator, ListFactoryBean, MapFactoryBean, MBeanExporter, MethodInvokingFactoryBean, MethodInvokingJobDetailFactoryBean, MethodLocatingFactoryBean, ObjectFactoryCreatingFactoryBean, OpenSessionInViewInterceptor, OpenSessionInViewInterceptor, PersistenceAnnotationBeanPostProcessor, PersistenceExceptionTranslationInterceptor, PersistenceExceptionTranslationPostProcessor, PreferencesPlaceholderConfigurer, PropertyPathFactoryBean, PropertyPlaceholderConfigurer, PrototypeTargetSource, ProxyFactoryBean, QuickTargetSourceCreator, ScopedProxyFactoryBean, ScriptFactoryPostProcessor, ServiceLocatorFactoryBean, ServletContextPropertyPlaceholderConfigurer, SetFactoryBean, SimpleBeanFactoryAwareAspectInstanceFactory, SimpleBeanTargetSource, ThreadLocalTargetSource, TransactionProxyFactoryBean

엄청나게 많습니다.

주로 다른 Bean을 lock up 할 수 있는 factory사 필요할 때 사용합니다. 특정 객체만을 뽑아낼 factory가 필요하다면 FactoryBean을 사용하면 되는데 이때도 이 인터페이스를 사용할 수 있기 때문에 위의 구현체들의 반정도는 XXXFactoryBean 입니다.

FactroyBean을 사용하는 이유는 ‘만들 수 없는 것’을 FactoryBean으로 만들기 이 글에서 확인할 수 있으며 주로 객체 생성에 복잡한 설정과 로직이 필요한 경우에 사용하며 따라서 다른 프레임워크와 연동하는 객체가 필요한 경우에 자주 사용하며 위의 구현체 중에 또 절반 정도는 그러한 구현체에 해당합니다.

BeanClassLoaderAware 테스트

이전 글에서 사용한 Bean 클레스에 BeanClassLoaderAware 인터페이스를 추가로 구현합니다.

public class BeanLifeCycleTestBean implements BeanNameAware, BeanClassLoaderAware{

    String beanName;

    ClassLoader classLoader;

    public void setBeanName(String beanName) {
        System.out.println(“setBeanName() 실행합니다.”);
        this.beanName = beanName;
    }

    public String getBeanName(){
        return beanName;
    }

    public void setBeanClassLoader(ClassLoader classLoader) {
        System.out.println(“setBeanClassLoader() 실행합니다.”);
        this.classLoader = classLoader;
    }

    public ClassLoader getClassLoader() {
        return classLoader;
    }

간단한 로깅을 위해서 문자열을 출력해 줍니다. 빈 설정은 바뀔 것이 없으며 테스트 코드만 약간 변경합니다.

public class BeanLifeCycleTest {

    private ApplicationContext context;
    private BeanLifeCycleTestBean bean;

    @Before
    public void setUp(){
        context = new ClassPathXmlApplicationContext(“net/agilejava/jedi/spring/beanLifeCycle/applicationContext.xml”);
        bean = (BeanLifeCycleTestBean) context.getBean(“test”);
    }

    @Test
    public void testBeanNameAware() {
        assertEquals(“test”, bean.getBeanName());
    }

    @Test
    public void testClassLoaderAware() {
        ClassLoader classLoader = bean.getClassLoader();
        assertNotNull(classLoader);
    }

}

Spring 소스코드에서 BeanClassLoader 인터페이스를 구현한 클레스들입니다.
– AbstractBeanFactory :: configurableBeanFactory 인터페이스 구현체
– AbstractBeanDefinitionReader :: Set the ClassLoader to use for bean classes. Default is null, which suggests to not load bean classes eagerly but rather to just register bean definitions with class names, with the corresponding Classes to be resolved later (or never).
– ConfigurableBeanFactory 인터페이스 :: Set the class loader to use for loading bean classes. Default is the thread context class loader. Note that this class loader will only apply to bean definitions that do not carry a resolved bean class yet. This is the case as of Spring 2.0 by default: Bean definitions only carry bean class names, to be resolved once the factory processes the bean definition.
– AbstractHttpInvokerRequestExecutor
– HttpInvokerTests

This is mainly intended to be implemented by framework classes which
have to pick up application classes by name despite themselves potentially
being loaded from a shared class loader.

ClassLoader 에 대한 학습이 필요하겠군요. 얼추 보니까 loadClass(“풀 패키지 이름 붙은 클레스명”) 을 사용하여 Class 객체를 받아 올 수 있습니다. 그 이후에는 리플렉션으로 이어지겠군요.

ClassLoader를 테스트 하기 위해 테스트 코드를 약간 수정했습니다.

    @Test
    public void testClassLoaderAware() throws ClassNotFoundException {
        ClassLoader classLoader = bean.getClassLoader();
        assertNotNull(classLoader);
        Class clazz = classLoader.loadClass(“net.agilejava.jedi.spring.beanLifeCycle.BeanLifeCycleTestBean”);
        assertEquals(BeanLifeCycleTestBean.class, clazz);
    }

BeanNameAware 테스트

public class BeanLifeCycleTestBean implements BeanNameAware{

    String beanName;

    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    public String getBeanName(){
        return beanName;
    }

}

위와 같이 BeanNameAware 클레스를 구현해 놓고 다음과 같이 간단히 Bean으로 등록합니다.

<bean id=”test” class=”net.agilejava.jedi.spring.beanLifeCycle.BeanLifeCycleTestBean” />

테스트 코드를 다음과 같이 작성하고 돌려보면…

public class BeanLifeCycleTest {

    private ApplicationContext context;

    @Test
    public void testBeanNameAware() {
        context = new ClassPathXmlApplicationContext(“net/agilejava/jedi/spring/beanLifeCycle/applicationContext.xml”);
        BeanLifeCycleTestBean bean = (BeanLifeCycleTestBean) context.getBean(“test”);
        assertEquals(“test”, bean.getBeanName());
    }

}

녹색 막대를 확인할 수 있습니다.

이 인터페이스를 구현한 Spring의 소스 코드로는 다음과 같은 것들이 있습니다.
– ServletForwardingController
– AbstractView :: Set the view’s name. Helpful for traceability.
– PortletWrappingController
– GenericFilterBean :: Stores the bean name as defined in the Spring bean factory. Only relevant in case of initialization as bean, to have a name as fallback to the filter name usually provided by a FilterConfig instance.
– JobDetailBean
– OsgiServiceProxyFactoryBean :: To find a bean published as a service by the OsgiServiceExporter, simply set this property. You may specify additional filtering criteria if needed (using the filter property) but this is not required.
– AbstractTest
– EhCacheFactoryBean
– FieldRetrievingFactoryBean :: The bean name of this FieldRetrievingFactoryBean will be interpreted as “staticField” pattern, if neither “targetClass” nor “targetObject” nor “targetField” have been specified. This allows for concise bean definitions with just an id/name.

각각의 소스코드 위에 붙어있던 JavaDoc을 보면 설정 파일에서 사용하는 bean 이름을 알면 유용한 경우가 있는 듯 합니다.
– Bean을 추적하거나 찾을 때 사용
– 특정 Bean 이름으로 필터링 할 때 사용합니다.

Interface to be implemented by beans that want to be aware of their
bean name in a bean factory. Note that it is not usually recommended
that an object depend on its bean name, as this represents a potentially
brittle dependence on external configuration, as well as a possibly
unnecessary dependence on a Spring API.

Spring API에서 인용한 부분을 보면 빈 설정의 빈 이름에 빈이 종속 되도록 하는 것을 권장하지 않습니다. 이유는 빈이 외부의 설정 파일에 종속되게 되며 이것은 Spring API에 종속되는 거나 마찬가지기 때문이라고 합니다.

3.4.3. The other scopes

2.0에 새로 추가 된 Bean의 Scope들로 request, session, global session이 있습니다. 그리고 이 Scope들은 웹에서 사용하도록 만들어진 것이기 때문에 web-based applicationContext에서만 사용할 수 있습니다. 안그러면 IllegalStateException 이게 발생합니다.

web-based applicationContext 란?
WebApplicationContext 인터페이스를 구현한 클래스들로 다음과 같습니다.

AbstractRefreshablePortletApplicationContext, AbstractRefreshableWebApplicationContext, GenericWebApplicationContext, StaticPortletApplicationContext, StaticWebApplicationContext, XmlPortletApplicationContext, XmlWebApplicationContext

물론 위에서 abstract와 generic(여러 확장 포인트를 제공하기 위해 만든 클래스들)과 static(테스트 용도로 만들어둔 클래스들)을 제외 하면 사실상 XmlPortletApplicationContext, XmlWebApplicationContext 이 두 개가 남습니다.


3.4.3.1. Initial web configuration

새로 추가된 웹 어플리케이션을 위한 scope들을 사용하려면 web.xml에 리스너 or 필터를 등록해야 합니다.

Sevlet 2.4 이상의 버젼을 사용할 때는 아래 처럼 리스너를 등록합니다.

<web-app>
  …
  <listener>
   <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
  </listener>
  …
</web-app>

Servlet 2.4 미만의 버젼을 사용할 때는 아래 처럼 필터를 등록합니다.

<web-app>
  ..
  <filter>
    <filter-name>requestContextFilter</filter-name>
    <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>requestContextFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  …
</web-app>


3.4.3.2. The request scope

<bean id=”loginAction” class=”com.foo.LoginAction” scope=”request”/>

매 요청 마다 해당 객체를 사용할 수 있으며 각각의 요청은 서로 다른 객체를 가지게 됩니다. 그리고 요청에 대한 처리가 끝나면(컨트롤러에서 해당 메소드가 종료 되면) 더 이상 사용할 수 없습니다.

3.4.3.3. The session scope

<bean id=”userPreferences” class=”com.foo.UserPreferences” scope=”session”/>

매 세션 마다 해당 객체를 사용할 수 있으며 각각의 세션은 서로 다른 객체를 가지게 됩니다. 그리고 세션이 닫히면(브라우저를 끄거나 타임오버가 되면) 더 이상 사용할 수 없습니다.

3.4.3.4. The global session scope

<bean id=”userPreferences” class=”com.foo.UserPreferences” scope=”globalSession”/>

위에서 설명한 session Scope과 동일하며 단지 포틀릿 어플리케이션에서만 사용할 수 있다는 차이가 있습니다. 포틀릿 어플리케이션이 뭔지 모르기 때문에 pass..

3.4.3.5. Scoped beans as dependencies

<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans”
       xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
       xmlns:aop=”http://www.springframework.org/schema/aop”
       xsi:schemaLocation=”
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd”>

    <!– a HTTP Session-scoped bean exposed as a proxy –>
    <bean id=”userPreferences” class=”com.foo.UserPreferences” scope=”session”>
         
          <!– this next element effects the proxying of the surrounding bean –>
          <aop:scoped-proxy/>
    </bean>
   
    <!– a singleton-scoped bean injected with a proxy to the above bean –>
    <bean id=”userService” class=”com.foo.SimpleUserService”>
   
        <!– a reference to the proxied ‘userPreferences’ bean –>
        <property name=”userPreferences” ref=”userPreferences”/>

    </bean>
</beans>

위와 같이 <aop:scoped-proxy/>를 항상 request, session, globalsession 빈을 만들 때 넣어줘야 합니다.

왜?
보통은 아래 처럼 DI하는 것을 생각할 수 있습니다.

<bean id=”userPreferences” class=”com.foo.UserPreferences” scope=”session”/>

<bean id=”userManager” class=”com.foo.UserManager”>
    <property name=”userPreferences” ref=”userPreferences”/>
</bean>

하지만 이때 userPreferences 빈은 scope이 session이지만 userManager의 scope이 singletone(default가 singleton이죠.)이기 때문에 문제가 발생합니다. 매 세션 마다 새로운 객체를 만들어 줘야 하지만 저 세션 객체를 사용하는 빈의 생성을 한 번 밖에 안하기 때문에 원하던 대로 동작하지 못합니다.
따라서 매 세션 마다 새로운 객체를 만들어서 줄 프록시를 만들기 위해서 <aop:scoped-proxy/>를 사용합니다.

3.4.3.5.1. Choosing the type of proxy created

<!– DefaultUserPreferences implements the UserPreferences interface –>
<bean id=”userPreferences” class=”com.foo.DefaultUserPreferences” scope=”session”>
    <aop:scoped-proxy proxy-target-class=”false”/>
</bean>

<bean id=”userManager” class=”com.foo.UserManager”>
    <property name=”userPreferences” ref=”userPreferences”/>
</bean>

Spring AOP는 프록시 기반이며 프록시를 만들 때 인터페이스를 기반으로 만들 거라면 JDK의 API를 사용하고 클래스 기반으로 만들 때에는 CGLib을 사용합니다.

위에서도 Spring AOP를 사용하고 있고 기본적으로 CGLib을 사용하여 프록시를 만들도록 설정되어 있습니다.

대상이 되는 객체가 어떤 인터페이스를 기반으로 만들어 졌고 JDK의 프록시 API를 사용하고 싶다면 위의 예제 코드 처럼 proxy-target-class 속성의 값에 false 를 넣어 주면 됩니다.

Circular dependencies

참조 : Spring Reference 3.3.1.2의 오른쪽 회색 박스

Circular dependencies[footnote]이전 글에서 모르겠다고 했던 circular reference를 다르게 IoC챕터 답게 표현한듯…[/footnote]는 간단하게 교착상태에 비유할 수 있을 것 같습니다.

Constructor Injection을 사용할 때 발생할 수 있는 문제로 예를 들어 A라는 클래스의 인자로 B 클래스 타입의 객체가 필요하고 B 객체를 만들 때 생성자의 인자로 A 타입의 객체가 필요하다면… 대체.. 어떻게 A와 B를 만들 수 있을까요??[footnote]저런 생성자들만 존재한다는 전제 조건이죠.[/footenote]

이런 경우 BeanCurrentlyInCreationException 이 발생한다고 합니다.

해결책은..Setter Injection을 사용하는거죠. 아니면 다른 객체를 인자로 받아들이는 생성자 말고 다른 생성자를 사용해서 생성하는 방법도 있지만 어차피 속성을 세팅하려면 Setter Injection을 써야겠습니다.

코드로 확인해보죠.

<bean id=”employee” class=”circularReference.Employee”>
    <constructor-arg ref=”project” />
</bean>

<bean id=”project” class=”circularReference.Project”>
    <constructor-arg ref=”employee”/>
</bean>

@Test public void name(){
        ApplicationContext context =
            new ClassPathXmlApplicationContext(“circularReference/circuralContext.xml”);
        assertNotNull(context.getBean(“project”));
    }

BeanCurrentlyInCreationException의 상위 타입인 BeanCreationException으로 나옵니다. 에러 메시지를 좀 더 읽다보면 보입니다.
사용자 삽입 이미지
아래는 에러 메시지의 Trace입니다.
[#M_ more.. | less.. | org.springframework.beans.factory.BeanCreationException: Error creating bean with name ’employee’ defined in class path resource [circularReference/circuralContext.xml]: Cannot resolve reference to bean ‘project’ while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘project’ defined in class path resource [circularReference/circuralContext.xml]: Cannot resolve reference to bean ’employee’ while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ’employee’: Requested bean is currently in creation: Is there an unresolvable circular reference?
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘project’ defined in class path resource [circularReference/circuralContext.xml]: Cannot resolve reference to bean ’employee’ while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ’employee’: Requested bean is currently in creation: Is there an unresolvable circular reference?
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ’employee’: Requested bean is currently in creation: Is there an unresolvable circular reference?
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:213)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:156)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:246)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:128)
    at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:332)
    at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:97)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:683)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:621)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:380)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:245)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:141)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:242)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:156)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:246)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:128)
    at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:332)
    at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:97)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:683)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:621)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:380)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:245)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:141)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:242)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:156)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:290)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:348)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:92)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:77)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:68)
    at circularReference.CircuralReferenceTest.name(CircuralReferenceTest.java:19)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.junit.internal.runners.TestMethodRunner.executeMethodBody(TestMethodRunner.java:99)
    at org.junit.internal.runners.TestMethodRunner.runUnprotected(TestMethodRunner.java:81)
    at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34)
    at org.junit.internal.runners.TestMethodRunner.runMethod(TestMethodRunner.java:75)
    at org.junit.internal.runners.TestMethodRunner.run(TestMethodRunner.java:45)
    at org.junit.internal.runners.TestClassMethodsRunner.invokeTestMethod(TestClassMethodsRunner.java:71)
    at org.junit.internal.runners.TestClassMethodsRunner.run(TestClassMethodsRunner.java:35)
    at org.junit.internal.runners.TestClassRunner$1.runUnprotected(TestClassRunner.java:42)
    at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34)
    at org.junit.internal.runners.TestClassRunner.run(TestClassRunner.java:52)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:38)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)

_M#]