머리 뽀개지는 BeanPostProcessor

이전 글에서 모든 Bean Life Cycle 콜백들을 구현하여 모든 콜백들이 예상대로 동작하는지 확인해봤습니다. 모든 메소드들이 제대로 동작했지만 BeanPostProcessor만 이상하게 동작하지 않았습니다.

결론부터 말씀드리면 재미없기 때문에 일단 코드를 보겠습니다.

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

    <bean id=”test2″ class=”net.agilejava.jedi.spring.beanLifeCycle.BeanLifeCycleTestBean” init-method=”customInit” />

이 전 글에서 테스트 할 때 사용한 클레스를 다른 빈으로 하나 더 등록하고 테스트를 돌렸습니다.

setBeanName() 실행합니다.
setBeanClassLoader() 실행합니다.
setBeanFactory() 실행합니다.
setResourceLoader() 실행합니다.
setApplicationEventPublisher() 실행합니다.
setMessageSource() 실행합니다.
setApplicationContext() 실행합니다.
afterPropertiesSet() 실행합니다.
customInit() 실행합니다.
setBeanName() 실행합니다.
setBeanClassLoader() 실행합니다.
setBeanFactory() 실행합니다.
setResourceLoader() 실행합니다.
setApplicationEventPublisher() 실행합니다.
setMessageSource() 실행합니다.
setApplicationContext() 실행합니다.
postProcessBeforeInitialization() 실행합니다.
afterPropertiesSet() 실행합니다.
customInit() 실행합니다.
postProcessAfterInitialization() 실행합니다.

자 이제 수수께기가 풀리기 시작합니다. 제가 녹색으로 칠한 부분이 test 빈에서 찍힌것이고 v하늘색은 test1 빈에서 찍힌것입니다.

BeanPostProcessor의 콜백 메소드들은 자신 이후에 생성되는 빈들에 적용됩니다.

좀 더 확실하게 확인하기 위해서 이번엔 세 개로 테스트 해보겠습니다.

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

    <bean id=”test2″ class=”net.agilejava.jedi.spring.beanLifeCycle.BeanLifeCycleTestBean” init-method=”customInit” />

    <bean id=”test3″ class=”net.agilejava.jedi.spring.beanLifeCycle.BeanLifeCycleTestBean” init-method=”customInit” />

결과는 다음과 같습니다,

setBeanName() 실행합니다.
setBeanClassLoader() 실행합니다.
setBeanFactory() 실행합니다.
setResourceLoader() 실행합니다.
setApplicationEventPublisher() 실행합니다.
setMessageSource() 실행합니다.
setApplicationContext() 실행합니다.
afterPropertiesSet() 실행합니다.
customInit() 실행합니다.
setBeanName() 실행합니다.
setBeanClassLoader() 실행합니다.
setBeanFactory() 실행합니다.
setResourceLoader() 실행합니다.
setApplicationEventPublisher() 실행합니다.
setMessageSource() 실행합니다.
setApplicationContext() 실행합니다.
postProcessBeforeInitialization() 실행합니다.
afterPropertiesSet() 실행합니다.
customInit() 실행합니다.
postProcessAfterInitialization() 실행합니다.
setBeanName() 실행합니다.
setBeanClassLoader() 실행합니다.
setBeanFactory() 실행합니다.
setResourceLoader() 실행합니다.
setApplicationEventPublisher() 실행합니다.
setMessageSource() 실행합니다.
setApplicationContext() 실행합니다.
postProcessBeforeInitialization() 실행합니다.
postProcessBeforeInitialization() 실행합니다.
afterPropertiesSet() 실행합니다.
customInit() 실행합니다.
postProcessAfterInitialization() 실행합니다.
postProcessAfterInitialization() 실행합니다.

하지만 bean 생성은 보통 설정 파일에 등록되어 있는 순서이고 depends-on 속성을 사용한다 하더라고 위 처럼 여러개의 BeanPostProcessor의 실행 순서를 예측하기란 사실 불안합니다.

그래서…BeanPostProcessor가 implements하고 있는 Orderd 인터페이스를 사용하여 순서를 지정할 수 있습니다.

BeanFactoryPostProcessor와의 차이는 BeanPostProcessor는 Bean 객체에 조작을 하는 반명 BeanFactoyPostProcessor는 Bean을 만드는 설명서에 해당하는 설정파일을 조작한다는 것입니다.

Bean Life Cycle 통째로 테스트

Bean Life Cycle
BeanNameAware 테스트
BeanClassLoaderAware 테스트
BeanFactoryAware 테스트
MessageSource 사용 예
ApplicationEvent 사용 예
MessageSource 사용 예
BeanPostProcessor 사용 예

다 비슷한 테스트 들인데 하나씩 테스트 하기가 지겨워서 Bean Life Cycle에 관여하는 모든 인터페이스를 전부 구현하도록 했습니다.

/**
 *
 */
package net.agilejava.jedi.spring.beanLifeCycle;

import javax.servlet.ServletContext;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
import org.springframework.web.context.ServletContextAware;

/**
 * @author keesun
 *
 */
public class BeanLifeCycleTestBean implements BeanNameAware,
    BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware,
    ApplicationEventPublisherAware, MessageSourceAware,
    ApplicationContextAware, ServletContextAware, BeanPostProcessor,
    InitializingBean{

    String beanName;

    ClassLoader classLoader;

    BeanFactory beanFactory;

    ResourceLoader resourceLoader;

    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 void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println(“setBeanFactory() 실행합니다.”);
        this.beanFactory = beanFactory;
    }

    public BeanFactory getBeanFactory() {
        return beanFactory;
    }

    public void setResourceLoader(ResourceLoader resourceLoader) {
        System.out.println(“setResourceLoader() 실행합니다.”);
        this.resourceLoader = resourceLoader;
    }

    public ResourceLoader getResourceLoader(){
        return resourceLoader;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher arg0) {
        System.out.println(“setApplicationEventPublisher() 실행합니다.”);
    }

    public void setMessageSource(MessageSource arg0) {
        System.out.println(“setMessageSource() 실행합니다.”);
    }

    public void setApplicationContext(ApplicationContext arg0) throws BeansException {
        System.out.println(“setApplicationContext() 실행합니다.”);
    }

    public void setServletContext(ServletContext servletContext){
        System.out.println(“setServletContext() 실행합니다.”);
    }

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(“postProcessAfterInitialization() 실행합니다.”);
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(“postProcessAfterInitialization() 실행합니다.”);
        return bean;
    }

    public void afterPropertiesSet() throws Exception {
        System.out.println(“afterPropertiesSet() 실행합니다.”);
    }

    public void customInit(){
        System.out.println(“customInit() 실행합니다.”);
    }
}

테스트에서는 getBean으로 해당 bean을 가져오기만 합니다. 그러면 다음과 같은 출력을 확인할 수 있습니다.

setBeanName() 실행합니다.
setBeanClassLoader() 실행합니다.
setBeanFactory() 실행합니다.
setResourceLoader() 실행합니다.
setApplicationEventPublisher() 실행합니다.
setMessageSource() 실행합니다.
setApplicationContext() 실행합니다.
afterPropertiesSet() 실행합니다.
customInit() 실행합니다.

어라.. 뭔가 빠졌습니다. 뭐가 빠졌을까요.. 구현한 것 중에 BeanPostProcessor와 관련된 callback들이 실행되지 않았습니다.

이론~ 왜이러는거야~~ 왜 BeanPostProcessor를 왕따시키는거야!!

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);
    }