on
빈과 빈 정의 커스텀
빈과 빈 정의 커스텀
반응형
빈과 빈 정의 커스텀 하기
빈에 커스텀 초기화와 정리 로직을 넣는 방법 스프링 BeanPostProcessor 인터페이스를 구현해 새로 생성된 빈 인스턴스와 상호작용하는 방법 스프링 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); } }
JVM에게 종료 훅을 등록할 수 있는 registerShutdownHook 메서드가 들어 있다.
종료 훅은 JVM이 종료될 때 ApplcationContext를 닫는 책임을 갖는다.
main 메서드 종료시 종료 훅이 캐시에 모든 싱글턴 빈 인스턴스를 제거하고 ApplcationContext 인스턴스를 닫는다.
프로토타입 스코프 빈의 경우 destory-method 속성을 무시한다. ApplcationContext에서 프로타입 빈을 얻어낸 객체가 자신이 사용한 프로토타입 스코프 빈의 해제 메서드를 명시적으로 호출할 책임을 지도록 스프링 컨테이너가 원하기 때문이다.
1-2. InitializingBean과 DisposableBean 생애주기 인터페이스 초기화
스프링 컨테이너는 ApplcationContextAware, InitializingBean, DisposableBean과 같은 생애주기 인터페이스를 구현하는 빈에 대해 콜백을 호출한다.
이런 콜백은 해당 빈이 어떤 동작을 수행하거나, 빈 인스턴스에 필요한 정보를 제공하기 위한 목적으로 쓰인다.
이런 콜백은 해당 빈이 어떤 동작을 수행하거나, 빈 인스턴스에 필요한 정보를 제공하기 위한 목적으로 쓰인다. ApplcationContextAware 인터페이스를 구현하면 컨테이너는 그 빈 인터페이스의 setAppliactionContext 메서드를 호출해 배치된 빈에 ApplcationContext에 대한 참조를 제공한다.
InitializingBean 인터페이스는 afterPropertiesSet 메서드가 들어 있다. 스프링 컨테이너는 빈 프로퍼티를 설정한 다음에 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를 설정한다.
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가 실행한 다음에 호출된다.
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 getBean(Class klass) { return applicationContext.getBean(klass); } }
MyApplicationContext 클래스는 스프링의 ApplcationContextAware 인터페이스를 구현해 빈이 배포된 스프링 컨테이너의 ApplcationContext 객체의 참조를 얻는다.
MyApplicationContext의 getBean 메서드는 ApplcationContext 인스턴스로부터 얻은 이름을 가지고 빈을 반환한다.
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, "[email protected]")); } }
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, 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 메서드가 호출되지 않는다.
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 메서드를 호출하면 참조되는 빈이 프로토타입 빈인지 알 수 있다.
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가 실제 값을 프로퍼티 파일에서 가져와 빈 정의의 프로퍼티 위치 지정자를 실제값으로 바꿔준다.
classpath:database.properties classpath:webservice.properties
PropertySourcesPlaceholderConfigurer의 locations 프로퍼티는 프로퍼티 위치 지정자의 값을 찾기 위해 검색하는 프로퍼티 파일 경로를 지정한다.
PropertySourcesPlaceholderConfigurer는 database.properties, webservice.properties 파일에서 프로퍼티 위치 지정자의 값을 찾는다.
ignoreUnresolvablePlaceholders 프로퍼티는 PropertySourcesPlaceholderConfigurer가 locations에 지정한 프로퍼티 파일에 프로퍼티 위치 지정자의 값을 찾지 못했을 경우, 무시할지, 예외를 발생시킬지 지정한다.
false로 설정되었다면 지정자 값을 찾지 못할 경우 PropertySourcesPlaceholderConfigurer가 예외를 발생시킨다.
localOverride 프로퍼티
classpath:database.properties classpath:webservice.properties locally-set-password locally-set-driverClass locally-set-webServiceUrl
PropertySourcesPlaceholderConfigurer의 properties 프로퍼티는 지역 프로퍼티를 정의한다.
localOverride는 외부 프로퍼티 파일에서 읽은 프로퍼티 보다 지역 프로퍼티를 더 우선시 할지 지정한다. true면 지역 프로퍼티를 더 우선적으로 사용한다.
PropertyOverrideConfigurer
외부 프로퍼티 파일에 빈 프로퍼티를 지정할 수 있다. classpath:database.properties classpath:webservice.properties
외부 프로퍼티 파일에서 빈 프로퍼티 값을 찾을 수 없는 경우에는 빈 프로퍼티의 디폴트 값이 유지된다.
PropertyOverrideConfigurer와 PropertySourcesPlaceholderConfigurer는 스프링 PropertyResourceConfigurer를 상속하기 때문에 두 클래스가 비슷한 설정 옵션을 제공한다.
배워서 바로 쓰는 스프링프레임워크
애시시 사린, 제이 샤르마 지음
오현석 옮김
반응형
from http://frontierdev.tistory.com/264 by ccl(A) rewrite - 2021-12-26 22:01:31