머리 뽀개지는 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을 만드는 설명서에 해당하는 설정파일을 조작한다는 것입니다.

BeanPostProcessor 사용 예

BeanPostProcessor 인터페이스는 다음과 같습니다.
사용자 삽입 이미지이 인터페이스를 구현한 클래스들 입니다. 유용하니까 만들어 뒀을 텐데 언제 어떤걸 사용하면 좋을지는 나~~~중에 알아봐야겠네요.

AbstractAdvisorAutoProxyCreator, AbstractAutoProxyCreator, ActionServletAwareProcessor, AdvisorAdapterRegistrationManager, AnnotationAwareAspectJAutoProxyCreator, ApplicationContextAwareProcessor, AspectJAwareAdvisorAutoProxyCreator, BeanNameAutoProxyCreator, DefaultAdvisorAutoProxyCreator, InstantiationAwareBeanPostProcessorAdapter, PersistenceAnnotationBeanPostProcessor, PersistenceExceptionTranslationPostProcessor, PortletContextAwareProcessor, RequiredAnnotationBeanPostProcessor, ScriptFactoryPostProcessor, ServletContextAwareProcessor, SimplePortletPostProcessor, SimpleServletPostProcessor

SIA(Spring In Action) 74쪽 부터 몇 장에 걸쳐 postProcessAfterInitialization 메소드를 사용하는 예제가 나와있습니다.

Reference에서는 이 인터페이스를 사용하는 예제라기 보다는 BeanFactory와 Applcation Context에 등록하는 방법을 주로 다루고 있습니다.

Member 객체의 id 속성 값이 “혜인이”이라면 “이쁘니”로 값을 바꾸는 예제를 만들어 보겠습니다.

    @Test public void beanPostProcessing(){
        Member keesun15 = getMember(“keesun15”);
        assertTrue(keesun15.getId().equals(“이쁘니”));
    }

설정 내역은 다음과 같습니다.

    <bean id=”keesun15″ class=”beanConfiguration.Member” >
        <property name=”id” value=”혜인이”>
    </bean>

테스트는 실패합니다. keesun15 bean의 id 속성 값이 “혜인이”이기 때문입니다.

KeesunPostProcessor 를 구현합니다.
[#M_ more.. | less.. |

 import java.lang.reflect.Field;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class KeesunPostProcessor implements BeanPostProcessor {

    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
        return bean;
    }

    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {

        System.out.println(“It’s now working”);

        try {
            Field idField = bean.getClass().getDeclaredField(“id”);
            idField.setAccessible(true);
            String id = (String) idField.get(bean);
            System.out.println(id);
            if(id.equals(“혜인이”))
                idField.set(bean, “이쁘니”);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return bean;
    }

}

_M#]그리고 설정 파일에 bean으로 등록 해주면 테스트가 통과 합니다.[footnote]이 객체를 받아와서 사용할 일은 없어 보이니까..id나 name을 굳이 적어줄 필요가 없겠네요.[/footnote]

<bean class=”beanConfiguration.KeesunPostProcessor” />

현재 사용중인 container가 ApplicationContext 종류이기 때문에 간단하게 bean으로 등록 해두기만 하면 container가 알아서 BeanPostProcessor를 읽어서 적용시켜 줍니다.

BeanFactory를 사용할 경우에는 Reference에 나온대로 addBeanPostProcessor를 소스 코드에서 등록해 줘야 합니다.

ConfigurableBeanFactory factory = new XmlBeanFactory(…);
           
    // now register any needed BeanPostProcessor instances
    MyBeanPostProcessor postProcessor = new MyBeanPostProcessor();
    factory.addBeanPostProcessor(postProcessor);

    // now start using the factory