반응형
빈과 빈 정의 커스텀 하기
- 빈에 커스텀 초기화와 정리 로직을 넣는 방법
- 스프링 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);
}
}
<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 메서드가 호출되지 않는다.
- 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를 상속하기 때문에 두 클래스가 비슷한 설정 옵션을 제공한다.
배워서 바로 쓰는 스프링프레임워크
애시시 사린, 제이 샤르마 지음
오현석 옮김
반응형