Vlidator – Property 파일 사용하기

이전 글에서 사용한 방법으로는 입력 필드에 값이 비어있는지만 확인할 수 있습니다. 아마도 Errors 객체를 사용할 때 값이 비어있는지 검사하려면 if 문이 자주 사용되니까 그 코드를 줄여주기 위해 만든것 같습니다.

따라서 빈 값인지 확인할 때는 유용하게 사용할 수 있지만 그 이외의 경우에는 Errors 인터페이스를 사용해야 한다.
사용자 삽입 이미지Errors의 인터페이스 중에 에러 메시지를 기록할 수 있는 메소드들입니다.

인자의 종류를 살펴보면 다음과 같습니다.

field :: 에러 메시지를 남길 대상이 되는 필드
errorCode :: 프로퍼티 파일에 있는 에러 메시지의 이름(키)
errorAgs :: 프로퍼티 파일에 있는 에러 메시지의 내용(값)을 출력할 때 특정 문자열을 넘겨 줄 수 있습니다. 그 때 이 아규먼트를 사용합니다.
defaultMessage :: 프로퍼티 파일에서 errorCode에 해당하는 메시지 키를 못찾으면 여기에 입력한 값을 출력합니다.

프로퍼티 파일을 사용하는 방법은 간단합니다.

    <bean id=”messageSource” class=”org.springframework.context.support.ResourceBundleMessageSource”>
        <property name=”basename” value=”message” />
    </bean>

위와 같이 messageSource를 등록합니다. 이 때 프로퍼티 파일의 이름을 basename에 입력해 줍니다. 만약 프로퍼티 파일이 여러개라면 besenames 속성을 사용하여 다음과 같이 list로 넘겨줍니다.

<beans>
  <bean id=”messageSource”
        class=”org.springframework.context.support.ResourceBundleMessageSource”>
    <property name=”basenames”>
      <list>
        <value>format</value>
        <value>exceptions</value>
        <value>windows</value>
      </list>
    </property>
  </bean>
</beans>

그 다음 프로퍼티 파일을 작성합니다. 위 설정에서 message 라는 이름을 입력했으니까 프로퍼티 파일은 message.properties 또는 message_ko_KR.properties 이런식으로 입력값 뒤에 지역코드가 붙은 프로퍼티 파일도 자동으로 읽히게 됩니다.

required=Input {0} Properties
passwordTooShort=Enter password at least 6 characters.
notSamePassword=Enter the same vlaue with password

프로퍼티에 저렇게 내용을 채우고 이제는 Validator를 만듭니다. 저번에 만든 코드에서 살짝 내용을 추가/수정했습니다.

public class MemberInfoValidator implements Validator{

    public boolean supports(Class clazz) {
        return MemberInfo.class.isAssignableFrom(clazz);
    }

    public void validate(Object object, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, “email”, “required”, new Object [] {“email”}, “Enter your email”);
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, “password”, “required”, new Object [] {“password”}, “Enter your password”);
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, “confirmPassword”, “required”, new Object [] {“confirmPassword”}, “Enter the same password for confirmation”);
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, “confirmMember”, “required”, new Object [] {“confirmMember”}, “Enter ajn member code”);
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, “name”, “required”, new Object [] {“name”}, “Enter your name”);

        MemberInfo memberInfo = (MemberInfo)object;
        if(memberInfo.getPassword().length() < 6)
            errors.rejectValue(“password”, “passwordTooShort”);

        if(!memberInfo.getPassword().equals(memberInfo.getConfirmPassword()))
            errors.rejectValue(“confirmPassword”, “notSamePassword”);
    }

}

이제 실행해보면 다음과 같이 에러메시지들이 출력됩니다.
사용자 삽입 이미지

Bean Life Cycle 콜백 인터페이스 사용 예(in Spring)

BeanNameAware
– ServletForwardingController
– AbstractView
– PortletWrappingController
– GenericFilterBean
– JobDetailBean
– OsgiServiceProxyFactoryBean
– AbstractTest
– EhCacheFactoryBean
– FieldRetrievingFactoryBean

BeanClassLoaderAware
– 구현 방법 : this.beanClassLoader = (beanClassLoader != null ? beanClassLoader : ClassUtils.getDefaultClassLoader());
– AbstractBeanFactory
– AbstractBeanDefinitionReader ::
– ConfigurableBeanFactory 인터페이스 ::
– AbstractHttpInvokerRequestExecutor
– HttpInvokerTests

BeanFactoryAware
– MethodLocatingFactoryBean
– ProxyFactoryBean
= AbstractAdvisorAutoProxyCreator
– AbstractBeanFactoryBasedTargetSourceCreator
– ScopedProxyFactoryBean
– AbstractBeanFactoryBasedTargetSource
– AutowiredAnnotationBeanPostProcessor
– ObjectFactoryCreatingFactoryBean
– ServiceLocatorFactoryBean
– DependencyInjectionAspectSupport
– 엄청난 분량

BeanPostProcessor :: before :: after
– BundleContextAwareProcessor :: 해당 bean이 bundleaware 일 때 BundleContext 세팅, 로깅, 없으면 예외처리 :: ::
– CommonsLogProviderBeanPostProcessor :: 리플렉션 사용해서 로깅 :: ::
– ServletContextAwareProcessor :: ServletContext랑 SevletConfig 세팅 :: ::
– PortletContextAwareProcessor
– AbstractAutoProxyCreator ::  :: 프록시 만들 때 사용

InitializingBean
– MustBeInitialized
– ServiceCreationFactoryBean
– CommonsLogFactoryBean :: 로깅
– FieldRetrievingFactoryBean :: 없으면 확인하고
– MethodInvokingFactoryBean ::
public void afterPropertiesSet() throws Exception {
        prepare();
        if (this.singleton) {
            this.initialized = true;
            this.singletonObject = doInvoke();
        }
    }
– PropertiesFactoryBean :: 싱글톤인지..
– ResourceFactoryBean :: resource 가 널이면 예외 발생
– 그 외에도 많아요.

ResourceLoaderAware
– ReloadableResourceBundleMessageSource :: Set the ResourceLoader to use for loading bundle properties files.
– DefaultPersistenceUnitManager
– ResourceJobSchedulingDataProcessor
– ScriptFactoryPostProcessor

ApplicationEventPublisherAware
– EventPublicationInterceptor :: invoke 호출할 때 이벤트 발생

MessageSourceAware
– Service :: 그냥 구현했네

ApplicationContextAware
– ApplicationObjectSupport :: applicationcontext 세팅하고 주는일 하는데 사용하는거 편하게 해주려고 만든 클레스
– SchedulerFactoryBean :: 스케쥴링 할 때 applicatoincontext 사용

ServletContextAware
-ServletContextAwareBean :: 기본 구현
– ServletContextAttributeExporter :: servletcontext 받아서 attributes Map을 채우네..
– ServletContextAttributeFactoryBean :: 위랑 같은데 역긴 attribute 하나만 채우네..
– ServletContextFactoryBean :: 기본 구현
– ServletContextParameterFactoryBean :: this.paramValue = servletContext.getInitParameter(this.initParamName); paramValue 세팅
– ServletContextPropertyPlaceholderConfigurer :: resolvePlaceholder 에서 사용
– WebApplicationObjectSupport :: 기본 구현
– CommonsMultipartResolver :: FileItemFactor에 Repository 위치 정보 세팅
– SimpleServletPostProcessor :: postProcessAfterInitialization 에서 사용

머리 뽀개지는 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를 왕따시키는거야!!