본문 바로가기

JAVA/Spring

빈과 빈 정의 커스텀

반응형

빈과 빈 정의 커스텀 하기

  1. 빈에 커스텀 초기화와 정리 로직을 넣는 방법
  2. 스프링 BeanPostProcessor 인터페이스를 구현해 새로 생성된 빈 인스턴스와 상호작용하는 방법
  3. 스프링 BeanFactoryPostProcessor 인터페이스를 구현해 빈 정의를 변경하는 방법

1-1. 빈에 커스텀 초기화와 정리 로직 커스텀 하기

  • bean 엘리먼트에 init-method 속성값을 지정, destory-method 속성값을 지정
public class FixedDespositDaoImpl implements FixedDespositDao {
    private static Logger logger = LogManager.getLogger(FixedDespositDaoImpl.class);
    private DatabaseConnection connection;

    public FixedDespositDaoImpl() {
        logger.info("FixedDespositDaoImpl's constructor invoked");
    }

    public void initializeDbConnection() {
        logger.info("FixedDespositDaoImpl's initializeDbConnection method invoked");
        connection = DatabaseConnection.getInstance();
    }

    public boolean createFixedDeposit(FixedDespositDetails fixedDespositDetails) {
        logger.info("FixedDespositDaoImpl's createFixedDeposit method invoked");

        return true;
    }

    public void releaseDbConnection() {
        logger.info("FixedDespositDaoImpl's releaseDbConnection method invoked");
        connection.releaseConnection();
    }
}
public class FixedDespositServiceImpl implements FixedDespositService {
    private static Logger logger = LogManager.getLogger(FixedDespositServiceImpl.class);
    private FixedDespositDao myFixedDepositDao;

    public void setMyFixedDepositDao(FixedDespositDao myFixedDepositDao) {
        logger.info("FixedDespositServiceImpl's setMyFixedDepositDao method invoked");
        this.myFixedDepositDao = myFixedDepositDao;
    }

    @Override
    public void createFixedDeposit(FixedDespositDetails fixedDespositDetails) throws Exception {
        //정기 예금을 만든다.
        myFixedDespositDao.createFixedDeposit(fixedDespositDetails);
    }
}
<bean id="fixedDespositService" class="sample.spring.service.FixedDespositServiceImpl">
    <property name="myFixedDepositDao" ref="myFixedDepositDao" />
</bean>

<bean id="fixedDespositDao" class="sample.spring.service.FixedDespositDaoImpl" init-method="initializeDbConnection" destory-method="releaseDbConnection" />
  • JVM에게 종료 훅을 등록할 수 있는 registerShutdownHook 메서드가 들어 있다.
  • 종료 훅은 JVM이 종료될 때 ApplcationContext를 닫는 책임을 갖는다.
  • main 메서드 종료시 종료 훅이 캐시에 모든 싱글턴 빈 인스턴스를 제거하고 ApplcationContext 인스턴스를 닫는다.
  • 프로토타입 스코프 빈의 경우 destory-method 속성을 무시한다. ApplcationContext에서 프로타입 빈을 얻어낸 객체가 자신이 사용한 프로토타입 스코프 빈의 해제 메서드를 명시적으로 호출할 책임을 지도록 스프링 컨테이너가 원하기 때문이다.

1-2. InitializingBean과 DisposableBean 생애주기 인터페이스 초기화

  • 스프링 컨테이너는 ApplcationContextAware, InitializingBean, DisposableBean과 같은 생애주기 인터페이스를 구현하는 빈에 대해 콜백을 호출한다.
    이런 콜백은 해당 빈이 어떤 동작을 수행하거나, 빈 인스턴스에 필요한 정보를 제공하기 위한 목적으로 쓰인다.
  • ApplcationContextAware 인터페이스를 구현하면 컨테이너는 그 빈 인터페이스의 setAppliactionContext 메서드를 호출해 배치된 빈에 ApplcationContext에 대한 참조를 제공한다.
  • InitializingBean 인터페이스는 afterPropertiesSet 메서드가 들어 있다. 스프링 컨테이너는 빈 프로퍼티를 설정한 다음에 afterPropertiesSet를 호출한다.
    빈은 afterPropertiesSet 메서드 안에서 데이터베이스 연결, 읽기 위한 파일 열기 등의 초기화를 수행할 수 있다.
  • DisposableBean 인터페이스에는 destory 메서드가 있고, 스프링 컨테이너는 빈 인스턴스가 제거될 때 destory를 호출한다.

1-3. JSR 250 @PostConstructor와 @PreDestory 애너테이션

  • JSR 250(자바 플랫폼 공통 애너테이션)에는 자바 기술에서 사용하는 표준 애너테이션 정의가 들어 있다.
public class FixedDespositDaoImpl implements FixedDespositDao {
    private static Logger logger = LogManager.getLogger(FixedDespositDaoImpl.class);
    private DatabaseConnection connection;

    public FixedDespositDaoImpl() {
        logger.info("FixedDespositDaoImpl's constructor invoked");
    }

    @PostConstructor
    public void initializeDbConnection() {
        logger.info("FixedDespositDaoImpl's initializeDbConnection method invoked");
        connection = DatabaseConnection.getInstance();
    }

    public boolean createFixedDeposit(FixedDespositDetails fixedDespositDetails) {
        logger.info("FixedDespositDaoImpl's createFixedDeposit method invoked");

        return true;
    }

    @PreDestory
    public void releaseDbConnection() {
        logger.info("FixedDespositDaoImpl's releaseDbConnection method invoked");
        connection.releaseConnection();
    }
}
  • 자바 9부터는 @PostConstructor, @PreDestory는 더이상 자바 SE에 포함되지 않는다.
  • 스프링 애플리케이션에서는 XML 파일 안에서 스프링 CommonAnnotationBeanPostProcessor를 설정한다.
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" />
  • CommonAnnotationBeanPostProcessor는 스프링의 BeanPostProcessor 인터페이스를 구현하며 JSR 250 애너테이션 처리를 책임진다.

2-1. BeanPostProcessor를 사용해 새로 생성된 빈 인스턴스와 상호 작용

  • BeanPostProcessor를 사용하면 새로 생성된 빈 인스턴스가 스프링 컨테이너에 의해 초기화 되기 전과 후에 상호 작용을 할 수 있다.
  • Object postProcessBeforeInitialization(Object bean, String beanName) : 빈 인스턴스의 초기화 메서드가 호출되기 전에 호출된다.
  • Object postProcessAfterInitialization(Object bean, String beanName) : 빈 인스턴스의 초기화 메서드가 호출된 다음에 호출된다.
public interface InstanceValidator {
    void validateInstance();
}
  • BeanPostProcessor 구현을 사용해 설정 검증을 수행하는 모든 빈이 반드시 구현해야 하는 인터페이스이다.
public class FixedDepositDaoImpl implements FixedDepositDao, InstanceValidator {
    private static Logger logger = LogManager.getLogger(FixedDepositDaoImpl.class);
    private DatabaseConnection connection;

    public FixedDepositDaoImpl() {
        logger.info("FixedDepositDaoImpl's constructor invoked");
    }

    public void initializeDbConnection() {
        logger.info("FixedDepositDaoImpl's initializeDbConnection method invoked");
        connection = DatabaseConnection.getInstance();
    }

    public boolean createFixedDeposit(FixedDepositDetails fdd) {
        logger.info("FixedDepositDaoImpl's createFixedDeposit method invoked");
        // -- save the fixed deposits and then return true
        return true;
    }

    public void releaseDbConnection() {
        logger.info("FixedDepositDaoImpl's releaseDbConnection method invoked");
        connection.releaseConnection();
    }

    @Override
    public void validateInstance() {
        logger.info("Validating FixedDepositDaoImpl instance");
        if(connection == null) {
            logger.error("Failed to obtain DatabaseConnection instance");
        }
    }
}
  • initializeDbConnection 메서드는 DatabaseConnection 클래스의 정적 메서드 getInstance를 호출해 DatabaseConnection 인스턴스를 얻어오는 초기화 메서드다.
  • FixedDespositServiceImpl 인스턴스가 DatabaseConnection 인스턴스를 가져오는데 실패하면 connection은 null이된다.
  • connection이 null이면 validateInstance 메서드는 FixedDespositServiceImpl 인스턴스가 제대로 초기화되지 않았음을 알려주는 메시지를 로그에 남긴다.
  • initializeDbConnection 초기화 메서드가 connection 속성값을 설정하기 때문에 validateInstance 메서드는 반드시 initializeDbConnection 메서드가 호출된 다음에 호출돼야 한다.
  • 실제 애플리케이션 개발 상황에서 빈 인스턴스가 제대로 설저오디지 않았다면 validateInstance 메서드가 이를 해결하기 위한 조치를 취하거나 런타임 예외를 던져 애플리케이션이 시작하지 못하게 막아야 할 것이다.
public class InstanceValidationBeanPostProcessor implements BeanPostProcessor,
        Ordered {
    private static Logger logger = LogManager
            .getLogger(InstanceValidationBeanPostProcessor.class);
    private int order;

    public InstanceValidationBeanPostProcessor() {
        logger.info("Created InstanceValidationBeanPostProcessor instance");
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        logger.info("InstanceValidationBeanPostProcessor's postProcessBeforeInitialization method invoked for bean " + beanName + " of type " + bean.getClass());
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
        logger.info("InstanceValidationBeanPostProcessor's postProcessAfterInitialization method invoked for bean " + beanName + " of type " + bean.getClass());
        if (bean instanceof InstanceValidator) {
            ((InstanceValidator) bean).validateInstance();
        }
        return bean;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    @Override
    public int getOrder() {
        return order;
    }
}
  • 이 클래스는 새로 생성된 빈에 대해 validateInstance 메서드를 호출하는 역할을 담당한다.
  • BeanPostProcessor 인터페이스와 Ordered 인터페이스를 구현하는 InstanceValidationBeanPostProcessor 클래스로 postProcessBeforeInitialization 메서드는 단지 전달받은 빈 인터페이스를 반환하기만 한다.
  • postProcessAfterInitialization 메서드에서는 빈 인스턴스가 InstanceValidator 타입인 경우 해당 빈 인스턴스의 validateInstance 메서드를 호출한다.
  • 빈이 InstanceValidator를 구현한 경우, 스프링이 빈 인스턴스의 초기화 메서드를 호출한 다음에 InstanceValidationBeanPostProcessor가 validateInstance 메서드를 호출한다는 뜻이다.
  • Ordered 인터페이스에는 getOrder 메서드 정의가 들어 있다.
  • 정수값을 반환한다. getOrder가 반환하는 정수값 XML 파일에 정의된 여러 BeanPostProcessor 사이에서 우선순위를 결정한다.
  • BeanPostProcessor는 getOrder가 반환하는 값이 클수록 우선순위가 낮으므로 반환하는 값이 작은 값을 가지는 BeanPostProcessor가 실행한 다음에 호출된다.
    <bean class="sample.spring.bankapp.postprocessor.InstanceValidationBeanPostProcessor">
        <property name="order" value="1" />
    </bean>

2-2. BeanPostProcessor - 빈 의존관계 해결

  • 스프링 ApplcationContextAware 인터페이스를 구현한 빈은 ApplcationContext의 getBean 메서드를 사용해 프로그램으로 빈 인스턴스을 얻을 수 있다.
  • ApplcationContextAware 인터페이스를 구현하면 애플리케이션 코드를 스프링과 결합시키므로 ApplcationContextAware 인터페이스를 구현하는 방식을 권장하지 않는다.
  • 빈에 ApplcationContext를 감싼 객체를 제공하는 BeanPostProcessor 구현방식을 사용하면 애플리케이션이 직접 스프링의 ApplcationContextAware나 ApplcationContext에 의존하지 않는다.
public interface DependencyResolver {
    void resolveDependency(MyApplicationContext myApplicationContext);
}
  • DependencyResolver에는 MyApplicationContext 객체를 받는 resolveDependency 메서드가 있다.
  • MyApplicationContext는 ApplcationContext 객체를 감싼다.
public class FixedDepositServiceImpl implements FixedDepositService,
        DependencyResolver {
    private static Logger logger = LogManager
            .getLogger(FixedDepositServiceImpl.class);
    private FixedDepositDao fixedDepositDao;

    @Override
    public void createFixedDeposit(FixedDepositDetails fdd) throws Exception {
        // -- create fixed deposit
        fixedDepositDao.createFixedDeposit(fdd);
    }

    @Override
    public void resolveDependency(MyApplicationContext myApplicationContext) {
        logger.info("Resolving dependencies of FixedDepositServiceImpl instance");
        fixedDepositDao = myApplicationContext.getBean(FixedDepositDao.class);
    }
}
  • FixedDepositServiceImpl은 FixedDepositDao 타입인 fixedDepositDao 속성을 정의한다.
  • resolveDependency 메서드는 MyApplicationContext로부터 FixedDepositDao 인스턴스를 얻어 FixedDepositDao 속성에 저장한다.
public class DependencyResolutionBeanPostProcessor implements BeanPostProcessor,
        Ordered {
    private MyApplicationContext myApplicationContext;
    private int order;
    private static Logger logger = LogManager
            .getLogger(DependencyResolutionBeanPostProcessor.class);

    public DependencyResolutionBeanPostProcessor() {
        logger.info("Created DependencyResolutionBeanPostProcessor instance");
    }

    public void setOrder(int order) {
        this.order = order;
    }

    @Override
    public int getOrder() {
        return order;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        logger.info("DependencyResolutionBeanPostProcessor's postProcessBeforeInitialization method invoked for bean " + beanName + " of type " + bean.getClass());
        if (bean instanceof DependencyResolver) {
            ((DependencyResolver) bean).resolveDependency(myApplicationContext);
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
        logger.info("DependencyResolutionBeanPostProcessor's postProcessAfterInitialization method invoked for bean " + beanName + " of type " + bean.getClass());
        return bean;
    }

    public void setMyApplicationContext(
            MyApplicationContext myApplicationContext) {
        this.myApplicationContext = myApplicationContext;
    }

}
  • DependencyResolutionBeanPostProcessor는 스프링 BeanPostProcessor와 Ordered 인터페이스를 구현한다.
  • myApplicationContext 속성은 DependencyResolutionBeanPostProcessor의 의존관계를 표현한다.
  • postProcessBeforeInitialization 메서드는 DependencyResolver 인터페이스를 구현하는 빈의 resolveDependency 메서드를 호출한다. 이때 MyApplicationContext 객체를 인수로 전달한다.
  • postProcessAfterInitialization 메서드는 단순히 받은 빈 인스턴스를 돌려준다.
public class MyApplicationContext implements ApplicationContextAware {
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        this.applicationContext = applicationContext;
    }

    public <T> T getBean(Class<T> klass) {
        return applicationContext.getBean(klass);
    }
}
  • MyApplicationContext 클래스는 스프링의 ApplcationContextAware 인터페이스를 구현해 빈이 배포된 스프링 컨테이너의 ApplcationContext 객체의 참조를 얻는다.
    MyApplicationContext의 getBean 메서드는 ApplcationContext 인스턴스로부터 얻은 이름을 가지고 빈을 반환한다.
    <bean class="sample.spring.bankapp.postprocessor.DependencyResolutionBeanPostProcessor">
        <property name="myApplicationContext" ref="myApplicationContext" />
        <property name="order" value="0" />
    </bean>

    <bean id="myApplicationContext" class="sample.spring.bankapp.common.MyApplicationContext" />
  • DependencyResolutionBeanPostProcessor 클래스의 빈 정의에서 order 프로퍼티값은 0이다. InstanceValidationBeanPostProcessor의 order 프로퍼티는 1이다.
  • 낮은 order 프로퍼티값이 더 높은 우선순위를 뜻하므로, 스프링 컨테이너는 InstanceValidationBeanPostProcessor에 빈 인스턴스를 적용하기 전에 DependencyResolutionBeanPostProcessor를 적용한다.
public class BankApp {
    public static void main(String args[]) throws Exception {
        ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("classpath:META-INF/spring/applicationContext.xml");

        FixedDepositService fixedDepositService = context.getBean(FixedDepositService.class);
        fixedDepositService.createFixedDeposit(new FixedDepositDetails(1, 1000, 12, "someemail@somedomain.com"));        
    }
}
  • ApplcationContext에서 FixedDepositService 인스턴스를 얻고, FixedDepositService의 createFixedDeposit 메서드를 실행한다.
  • 스프링 컨테이너가 DependencyResolutionBeanPostProcessor와 InstanceValidationBeanPostProcessor 빈을 XML 파일에 정의된 다른 모든 빈모다 먼저 생성한다.
  • 그리고 InstanceValidationBeanPostProcessor보다 먼저 DependencyResolutionBeanPostProcessor가 새로 생성된 빈에 적용된다.
  • 스프링 컨테이너가 BeanPostProcessor를 구현을 다른 BeanPostProcessor 구현에 적용하지 않는다.
  • 스프링이 InstanceValidationBeanPostProcessor의 인스턴스를 만들 때 DependencyResolutionBeanPostProcessor의 postProcessBeforeInitialization과 postProcessAfterInitialization 메서드를 호출하지 않는다.

2-3. FactoryBean에 대한 BeanPostProcessor의 동작방식

  • 스프링 컨테이너가 FactoryBean 인스턴스를 생성할때 BeanPostProcessor와 postProcessBeforeInitialization, postProcessAfterInitialization이 호출되는 내용
  • FactoryBean이 만들어내는 빈 인스턴스에 대해 오직 postProcessAfterInitialization만 호출되는 부분
public class EventSenderFactoryBean implements FactoryBean<EventSender>,
        InitializingBean {
    private static Logger logger = LogManager
            .getLogger(EventSenderFactoryBean.class);

    public EventSenderFactoryBean() {
        logger.info("Created EventSenderFactoryBean");
    }

    @Override
    public EventSender getObject() throws Exception {
        logger.info("getObject method of EventSenderFactoryBean invoked");
        return new EventSender();
    }

    @Override
    public Class<?> getObjectType() {
        return EventSender.class;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        logger.info("afterPropertiesSet method of EventSenderFactoryBean invoked");
    }

}
  • EventSenderFactoryBean 클래스는 스프링의 InitializingBean과 FactoryBean 인터페이스를 구현한다.
  • getObject 메서드는 EventSender 객체 인스턴스를 반환, isSingleton 메서드는 flase로 반환으로 EventSenderFactoryBean이 EventSender 객체를 요청할때마다 EventSenderFactoryBean의 getObject 메서드가 호출된다.
    public static void main(String args[]) throws Exception {
        ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(
                "classpath:META-INF/spring/applicationContext.xml");
        context.getBean("eventSenderFactory");
        context.getBean("eventSenderFactory");
        context.close();
    }
  • 스프링 컨테이너에 의해 EventSenderFactoryBean 인스턴스가 생성될때 BeanPostProcessor의 postProcessBeforeInitialization, postProcessAfterInitialization 메서드도 호출된다.
  • EventSenderFactoryBean이 만드는 EventSender 인스턴스에 대해 BeanPostProcessor의 postProcessAfterInitialization 메서드만 호출된다.

2-4. RequiredAnnotationBeanPostProcessor

  • 빈 프로퍼티의 세터 메서드에 스프링 @Required를 설정하면 스프링의 RequiredAnnotationBeanPostProcessor는 빈 프로퍼티가 XML에서 제대로 설정되었는지 검사한다.
public class FixedDepositServiceImpl implements FixedDepositService {
    private FixedDepositDao fixedDepositDao;

    @Required
    public void setFixedDepositDao(FixedDepositDao fixedDepositDao) {
        this.fixedDepositDao = fixedDepositDao;
    }
}
  • fixedDepositDao 프로퍼티에 대한 setFixedDepositDao 세터 메서드에 @Required 애노테이션을 설정한다.
  • XML 파일안에 RequiredAnnotationBeanPostProcessor를 정의하면 RequiredAnnotationBeanPostProcessor가 fixedDepositDao 프로퍼티에 설정할 값을 를 통해 지정했는지 검사한다.
  • XML 파일에 fixedDepositDao 프로퍼티를 설정하지 않았다면 예외를 발생한다.
  • RequiredAnnotationBeanPostProcessor는 빈 프로퍼티가 빈 정의에 설정되었지만 검사한다.
  • RequiredAnnotationBeanPostProcessor는 설정된 프로퍼티가 올바른지 확인하지 않는다.
  • 프로퍼티 값을 올바른 값이 아닌 null로 설정할 수도 있다. 이런 이유로 프로퍼티 값이 제대로 설정됐는지 확인하려면 초기화 메서드를 구현할 필요가 있다.

2-5. DestructionAwareBeanPostProcessor

  • BeanPostProcessor 구현을 사용해 새로 생성한 빈 인스턴스와 상호작용했다.
  • 경우에 따라 빈이 제거되기 전에 제거될 빈 인스턴스와 상호작용을 하고 싶을 경우 DestructionAwareBeanPostProcessor 인터페이스를 구현한 빈을 XML파일에서 설정해야 한다.
  • 프로토타입 빈의 경우 postProcessBeforeDestruction 메서드가 호출되지 않는다.
  1. BeanFactoryPostProcessor를 사용해 빈 정의 변경
  • 빈 정의를 변경하고 싶은 클래스는 스프링 BeanFactoryPostProcessor 인터페이스를 구현한다.
  • BeanFactoryPostProcessor는 스프링 컨테이너가 빈 정의를 로드한 다음, 빈 인스턴스를 만들어내기 전에 실행한다.
  • XML파일에 정의된 모든 빈이 생성되기 전에 생성된다.
  • 따라서 BeanFactoryPostProcessor에 다른 빈의 빈 정의를 변경할 기회가 주어진다.
  • 다른 스프링빈과 마찬가지로 XML에 BeanFactoryPostProcessor를 정의하면된다.
  • 빈 정의가 아니라 빈 인스턴스 자체와 상호작용 하려면 BeanFactoryPostProcessor가 아닌 BeanPostProcessor를 사용해야 한다.
  • BeanFactoryPostProcessor 인터페이스에는 postProcessBeanFactory 메서드만 들어 있다.
  • ConfigurableListableBeanFactory 타입의 인수를 받는다. 이 인수를 사용해 스프링 컨테이너가 로드한 빈 정의를 얻어서 변경할 수 있다.
  • postProcessBeanFactory안에서 ConfigurableListableBeanFactory의 getBean 메서드를 호출해 빈 인스턴스를 만들수 있지만, postProcessBeanFactory 안에서 빈을 만드는 것을 권장하지 않는다.
  • postProcessBeanFactory 메서드 안에서 생성된 빈 인스턴스에 대해 BeanPostProcessors의 메서드들이 호출되지 않는다.
  • ConfigurableListableBeanFactory는 ApplcationContext와 마찬가지로 스프링 컨테이너에 접근할 수 있게 해준다.
  • 스프링 컨테이너를 설정하고 모든 빈을 이터레이션하며, 빈 정의를 변경할 수 있게 해준다.
  • ConfigurableListableBeanFactory 객체를 사용하면 PropertyEditorRegistrars 등록과 BeanPostProcessor 등록 등의 일을 할 수 있다.

3-1 BeanFactoryPostProcessor

public class ApplicationConfigurer implements BeanFactoryPostProcessor {
    private static Logger logger = LogManager
            .getLogger(ApplicationConfigurer.class);

    public ApplicationConfigurer() {
        logger.info("Created ApplicationConfigurer instance");
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
        // -- get all the bean definitions
        for (int i = 0; i < beanDefinitionNames.length; i++) {
            String beanName = beanDefinitionNames[i];
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
            beanDefinition.setAutowireCandidate(false);
            // -- obtain dependencies of a bean
            if (beanDefinition.isSingleton()) {
                if (hasPrototypeDependency(beanFactory, beanDefinition)) {
                    logger.error("Singleton-scoped " + beanName + " bean is dependent on a prototype-scoped bean.");
                }
            }
        }
    }

    //-- Checks if a bean contains a prototype-scoped dependency
    private boolean hasPrototypeDependency( ConfigurableListableBeanFactory beanFactory, BeanDefinition beanDefinition) {
        boolean isPrototype = false;
        MutablePropertyValues mutablePropertyValues = beanDefinition.getPropertyValues();
        PropertyValue[] propertyValues = mutablePropertyValues.getPropertyValues();
        for (int j = 0; j < propertyValues.length; j++) {
            if (propertyValues[j].getValue() instanceof RuntimeBeanReference) {
                String dependencyBeanName = ((RuntimeBeanReference) propertyValues[j].getValue()).getBeanName();
                BeanDefinition dependencyBeanDef = beanFactory.getBeanDefinition(dependencyBeanName);
                if (dependencyBeanDef.isPrototype()) {
                    isPrototype = true;
                    break;
                }
            }
        }
        return isPrototype;
    }
}
  • postProcessBeanFactory 메서드는 ConfigurableListableBeanFactory의 getBeanDefinitionNames 메서드를 호출해서 스프링 컨테이너가 로드한 모든 빈 정의의 이름을 얻는다.(bean의 id 속성)
  • 모든 빈 정의의 이름을 가져오면 postProcessBeanFactory 메서드는 ConfigurableListableBeanFactory의 getBeanDefinition 메서드를 호출해서 각 빈 정의에 해당하는 BeanDefinition을 얻는다.
  • BeanDefinition 객체는 빈 정의를 표현하며, 이 객체를 변경하면 빈 설정을 변경할 수 있다. postProcessBeanFactory 메서드는 스프링 컨테이너가 로드한 모든 빈 정의에 대해 BeanDefinition의 setAutowireCandidate 메서드는 스프링 컨테이너가 로드한 모든 빈 정의에 대해 BeanDefinition의 setAutowireCandidate를 호출해서 모든 빈이 자동연결 대상으로 되지 않게 한다.
  • BeanDefinition의 isSingleton 메서드는 해당 빈 정의가 싱글톤 빈의 빈 정의인 경우 true로 반환하고 빈 정의가 싱글톤 빈 정의라면 postProcessBeanFactory 메서드는 hasPrototypeDependency 메서드를 호출해서 싱글턴 빈이 다른 프로토타입 빈에 의존하는지 검사한다.
  • 싱글턴 빈이 프로토타입 빈에 의존하면 postProcessBeanFactory 메서드는 로그에 오류 메시지를 남긴다.
  • hasPrototypeDependency 메서드는 BeanDefinition 인수가 표현하는 빈이 프로토타입 빈에 의존하는지 검사한다.
  • ConfigurableListableBeanFactory 인수는 스프링 컨테이너가 로드한 빈 정의에 접근할 수 있게 해준다.
  • hasPrototypeDependency 메서드는 BeanDefinition의 getPropertyValues 메서드를 호출해서 property 엘리먼트에 정의된 프로퍼티를 얻는다.
  • BeanDefinition의 getPropertyValues는 MutablePropertyValues 타입의 객체를 반환하는데, 이 객체를 변경하면 빈 프로퍼티를 변경할 수 있다.
  • 모든 빈 프로퍼티에 대해 이터레이션하면서 프로토타입 빈을 참조하는 빈 프로퍼티가 있는지 찾고 싶기 때문에 MutablePropertyValues의 getPropertyValues 메서드를 호출해서 PropertyValue 객체로 이뤄진 배열을 얻는다.
  • PropertyValue 객체에는 빈 프로퍼티 정보가 담겨 있다. 빈 프로퍼티가 스프링 빈을 가리키면 PropertyValue의 getValue 메서드를 호출해서 프로퍼티가 참조하는 빈의 이름이 들어 있는 RuntimeBeanReference객체를 얻는다.
  • 스프링 빈을 참조하는 빈 프로퍼티에만 관심있기 때문에 PropertyValue의 getValue 메서드가 반환하는 값을 검사해서 RuntimeBeanReference 타입의 인스턴스인지 본다.
  • RuntimeBeanReference의 getBeanName 메서드를 호출해서 참조 대상의 빈 이름을 얻는다.
  • 빈 프로퍼티가 참조하는 빈 이름을 얻었으면, ConfigurableListableBeanFactory의 getBeanDefinition을 호출하면 빈 참조에 해당하는 BeanDefinition 객체를 얻고 isPrototype 메서드를 호출하면 참조되는 빈이 프로토타입 빈인지 알 수 있다.
    <bean id="fixedDepositDao"
        class="sample.spring.bankapp.dao.FixedDepositDaoImpl"
        init-method="initializeDbConnection" destroy-method="releaseDbConnection">
        <property name="fixedDepositDetails" ref="fixedDepositDetails" />
    </bean>

    <bean id="fixedDepositDetails"
        class="sample.spring.bankapp.domain.FixedDepositDetails"
        scope="prototype" />

    <bean
        class="sample.spring.bankapp.postprocessor.InstanceValidationBeanPostProcessor">
        <property name="order" value="1" />
    </bean>

    <bean class="sample.spring.bankapp.postprocessor.ApplicationConfigurer" />
  • fixedDepositDao 싱글턴 빈은 fixedDepositDetails 프로토타입 빈의 의존한다.
INFO  sample.spring.bankapp.postprocessor.ApplicationConfigurer - Created ApplicationConfigurer instance
ERROR sample.spring.bankapp.postprocessor.ApplicationConfigurer - Singleton-scoped fixedDepositDao bean is dependent on a prototype-scoped bean.
INFO  sample.spring.bankapp.postprocessor.InstanceValidationBeanPostProcessor - Created InstanceValidationPostProcessor instance
  • ApplicationConfigurer를 만들고, InstanceValidationBeanPostProcessor 인스턴스를 생성하기 전에 ApplicationConfigurer의 postProcessBeanFactory 메서드를 실행한다.
  • BeanFactoryPostProcessor 인터페이스를 구현하는 빈이 BeanPostProcessor 인터페이스를 구현하는 빈보다 먼저 처리된다.
  • BeanPostProcessor를 사용해 BeanFactoryPostProcessor 인스턴스를 변경할 수 없다.
  • BeanFactoryPostProcessor를 사용하면 스프링 컨테이너가 로드한 빈 정의를 변경할 수 있고, BeanPostProcessor를 사용하면 새로 생성된 빈 인스턴스를 변경할 수 있다.

3-2. PropertySourcesPlaceholderConfigurer

  • PropertySourcesPlaceholderConfigurer(BeanFactoryPostProcessor를 구현함)을 사용하면 빈 프로퍼티나 생성자 인수값을(.properties) 프로퍼티 파일에 지정할 수 있다.
  • ${프로퍼티파일에 있는 프로퍼티 이름}
  • 스프링 컨테이너가 빈 정의를 로드할 때, PropertySourcesPlaceholderConfigurer가 실제 값을 프로퍼티 파일에서 가져와 빈 정의의 프로퍼티 위치 지정자를 실제값으로 바꿔준다.
    <bean id="datasource" class="sample.spring.chapter05.domain.DataSource">
        <property name="url" value="${database.url}" />
        <property name="username" value="${database.username}" />
        <property name="password" value="${database.password}" />
        <property name="driverClass" value="${database.driverClass}" />
    </bean>

    <bean id="webServiceConfiguration" class="sample.spring.chapter05.domain.WebServiceConfiguration">
        <property name="webServiceUrl" value="${webservice.url}" />
    </bean>

    <bean
        class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:database.properties</value>
                <value>classpath:webservice.properties</value>
            </list>
        </property>
        <property name="ignoreUnresolvablePlaceholders" value="false" />
    </bean>
  • PropertySourcesPlaceholderConfigurer의 locations 프로퍼티는 프로퍼티 위치 지정자의 값을 찾기 위해 검색하는 프로퍼티 파일 경로를 지정한다.
  • PropertySourcesPlaceholderConfigurer는 database.properties, webservice.properties 파일에서 프로퍼티 위치 지정자의 값을 찾는다.
  • ignoreUnresolvablePlaceholders 프로퍼티는 PropertySourcesPlaceholderConfigurer가 locations에 지정한 프로퍼티 파일에 프로퍼티 위치 지정자의 값을 찾지 못했을 경우, 무시할지, 예외를 발생시킬지 지정한다.
  • false로 설정되었다면 지정자 값을 찾지 못할 경우 PropertySourcesPlaceholderConfigurer가 예외를 발생시킨다.

localOverride 프로퍼티

    <bean
        class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:database.properties</value>
                <value>classpath:webservice.properties</value>
            </list>
        </property>
        <property name="properties">
            <props>
                <prop key="database.password">locally-set-password</prop>
                <prop key="database.driverClass">locally-set-driverClass</prop>
                <prop key="webservice.url">locally-set-webServiceUrl</prop>
            </props>
        </property>
        <property name="ignoreUnresolvablePlaceholders" value="false" />
        <property name="localOverride" value="true" />
    </bean>
  • PropertySourcesPlaceholderConfigurer의 properties 프로퍼티는 지역 프로퍼티를 정의한다.
  • localOverride는 외부 프로퍼티 파일에서 읽은 프로퍼티 보다 지역 프로퍼티를 더 우선시 할지 지정한다. true면 지역 프로퍼티를 더 우선적으로 사용한다.

PropertyOverrideConfigurer

  • 외부 프로퍼티 파일에 빈 프로퍼티를 지정할 수 있다.
      <bean
          class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
          <property name="locations">
              <list>
                  <value>classpath:database.properties</value>
                  <value>classpath:webservice.properties</value>
              </list>
          </property>
      </bean>
  • 외부 프로퍼티 파일에서 빈 프로퍼티 값을 찾을 수 없는 경우에는 빈 프로퍼티의 디폴트 값이 유지된다.
  • PropertyOverrideConfigurer와 PropertySourcesPlaceholderConfigurer는 스프링 PropertyResourceConfigurer를 상속하기 때문에 두 클래스가 비슷한 설정 옵션을 제공한다.

 

배워서 바로 쓰는 스프링프레임워크
애시시 사린, 제이 샤르마 지음
오현석 옮김

반응형

'JAVA > Spring' 카테고리의 다른 글

캐싱  (0) 2022.01.28
데이터베이스 연결  (0) 2022.01.05
의존관계주입  (0) 2021.12.26
빈 설정  (0) 2021.12.22
IoC 컨테이너 계층 구조 - 토비의 스프링  (0) 2020.09.25