빈 후처리기라고 얘기를 하면 먼저 떠오르는 것은 @PostConstruct가 떠오를 것이다. 빈을 초기화하는데에 있어서, 초기화 이후 어떠한 작업을 추가해서 하기 위한 애노테이션인데 이 또한 빈 후처리기에 속한다.
오늘은 스프링의 빈 후처리기가 무엇인지 살펴보자.
Bean PostProcessor(빈 후처리기)
빈 후처리기란 빈 저장소에 사용될 목적으로 생성할 객체를 빈 저장소에 등록되기 직전에 조작하는 것이다.
따라서, 빈 객체가 스프링 컨테이너에 등록되기 전에 저장 될 객체를 다른 객체로 바꾼다거나, 프록시를 적용한다거나 여러가지가 가능하다.
빈 후처리기 적용 과정
빈 후처리기의 적용 과정은 크게 4가지로 구분 할 수 있다.
1. 생성 : 스프링 빈 대상이 되는 객체를 생성한다.
2. 전달 : 생성된 객체를 빈 후처리기에 전달한다.
3. 후 처리 작업 : 후 처리기가 스프링 빈 객체를 조작한다.
4. 등록 : 빈 후처리기 작업 이후 빈을 스프링 컨테이너에 등록한다.
이를 그림으로 보면 다음과 같다.
후 처리 작업을 하는 부분이 가장 큰 포인트이다. 해당 작업에서 객체가 일련의 작업으로 부가 기능이 추가되거나 변경이 발생할 수 있기 때문이다.
빈 후처리기 구현
빈 후처리기 구현은 BeanPostProcessor 인터페이스를 구현함으로써 가능하다.
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
실제 BeanPostProcessor의 인터페이스 내부를 살펴보면 다음과 같은 2개의 메서드를 볼 수 있다. default 키워드를 사용함으로써 특별히 오버라이딩 하지 않으면 객체 그대로를 반환한다.
메서드 2개의 사용 시기는 다음과 같다.
1. postProcessBeforeInitialization() : InitializingBean 적용 이전, @PostConstruct 적용 이전에 적용되는 메서드이다.
2. postProcessAfterInitialization() : InitializingBean 적용 이후, @PostConstruct 적용 이후에 적용되는 메서드이다.
그리고 2개의 매개변수는 동일한데
1. bean : 등록하는 새로운 빈 인스턴스
2. beanName : 빈의 이름
이렇게 두 가지를 매개변수로 가지고 있다. 따라서 기존의 프록시 패턴을 사용할 때 무조건 추가해야했던 target 을 적용 시 알아서 판단해서 적용해준다.
실제로 빈 객체가 등록되기 전에 객체를 교체하는 예시를 살펴보자.
@Slf4j
@Configuration
static class BeanProcessorConfig {
@Bean(name = "beanA")
public A a() {
return new A();
}
@Bean
public AtoBPostProcessor helloPostProcessor() {
return new AtoBPostProcessor();
}
}
@Slf4j
static class AtoBPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
log.info("beanName={} bean={}", beanName, bean);
if (bean instanceof A) {
return new B();
}
return bean;
}
}
BeanPostProcessor 인터페이스를 구현하여 빈이 등록될 때 Object가 A로 형 변환이 가능하다면, B 객체로 빈을 등록하는 빈 후처리기를 볼 수 있다. (기존에 A 클래스와 B 클래스는 존재한다.)
@Test
void basicConfig() {
// 스프링 컨테이너 생성
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanProcessorConfig.class);
// beanA 이름으로 B 객체가 빈으로 등록
Object bean = applicationContext.getBean("beanA");
if(bean instanceof B) {
bean = (B) bean;
}
log.info("bean {}", bean.getClass());
// A 클래스로 등록했지만, B 클래스로 변경하여 테스트 성공
Assertions.assertThatThrownBy(() -> applicationContext.getBean(A.class))
.isInstanceOf(NoSuchBeanDefinitionException.class);
}
// 주요 로그
beanName=beanA bean=hello.proxy.postprocessor.BeanPostProccessorTest$A@3a0807b7
bean class hello.proxy.postprocessor.BeanPostProccessorTest$B
bean 생성 시 이름을 beanA로 지정을 해주었고, 해당 이름으로 빈 객체를 반환한다. 그리고 해당 Object 객체가 B 객체로 형변환이 가능하다면 B 객체로 변환하고, 클래스 정보를 확인하는 로그와 등록된 객체가 A 클래스가 아닌지 확인하는 테스트 코드가 있다.
로그를 살펴보면 빈 처리기에 실행된 로그는 분명 내부클래스 A를 등록한 것이 분명한데, 적용 이후 출력하는 것을 보면 B에 대한 클래스 정보가 나타나는 것을 볼 수 있다. 빈 후처리기가 정확하게 동작한 것이다. 정말 B 인스턴스인지 의문일 수 있기 때문에 실제로 A 클래스에 대한 테스트를 진행하여도 통과하는 것을 볼 수 있다. 정말 쉽게 중간에서 빈을 가로채서 정보를 수정할 수 있게 되었다.
또한 빈 후처리기의 가장 큰 장점은 @Component 를 사용하여 빈을 생성하는 컴포넌트 스캔에도 적용 할 수 있다는 것이다.
하지만 주의해야 할 점 또한 존재한다.
빈 후처리기 주의사항
빈 후처리기는 스프링 컨테이너에 빈을 등록할 때 적용을 하게 되는데 범위를 지정하지 않으면(포인트 컷을 사용하지 않으면) 스프링 컨테이너에 등록되는 모든 빈에 대해 빈 후처리기가 적용된다.
이는 굉장히 위험한게 스프링 부트는 개발자가 직접 빈을 등록하는 것을 제외하고도 스프링을 실행 할 때 스프링 부트가 자체적으로 등록하는 스프링 빈이 엄청 많다. 이와 같은 상황에 빈 후처리기에 프록시가 적용된다면, 실제로 적용되지 못하는 클래스들도 존재하기 때문에 예외가 발생할 수 있다는 문제가 발생하는 것이다.
따라서, 빈 후처리기 사용 시에는 적절한 필터링 즉, 포인트컷을 해주는 것이 중요하다.
빈 후처리기는 총 2번의 필터를 거치는데 이는 다음과 같다.
1. 프록시 생성단계 : 클래스와 메서드에 프록시를 생성할지에 대해서 검사한다. 이 때, 모든 메서드를 검사하면서 하나라도 빈 후처리기가 적용되어야 한다면 프록시를 생성한다.
2. 어드바이스 적용단계 : 프록시 생성 단계를 통해 프록시가 생성되었을 때, 포인트컷을 재 확인하여 어드바이스를 적용 할지에 대해서 확인을 해준다.
스프링의 자동 빈 후처리기
우리는 위에서 빈 후처리기를 직접 구현했지만, 스프링부트는 빈 후처리기를 자동으로 해주는 것이 존재한다.
바로 AnnotationAwareAspectJAutoProxyCreator라는 클래스이고, 실제로 다이어그램을 살펴보면 BeanPostProcessor를 인터페이스로 가지고 있는 것을 볼 수 있다.
이 클래스는 빈으로 Advisor를 등록하면 해당 Advisor를 보고 빈 후처리기가 직접 등록을 해주고 만약 @Aspect 애노테이션을 사용하여 등록한다면 BeanFactoryAspectJAdvisorsBuilder라는 내부 캐시에 저장하여 호출 할 수 있도록 해준다.
@Aspect는 후에 AOP를 설명할 때 후에 포스팅 해보려고 한다.
그럼 자동 빈 후처리기가 어떻게 등록되는지 살펴보자.
자동 빈 후처리기 적용 과정
1. 생성 : 수동이든 컴포넌트 스캔이든 빈을 등록하기 위한 객체를 생성한다.
2. 전달 : 생성 된 객체를 자동 빈 후처리기에 전달한다.
3. 조회 : Advisor 빈과 @AspectJ가 있는지 확인한다.
4. 프록시 생성 : PointCut을 통해서 적절하게 프록시를 생성한다.
5. 등록 : 스프링 컨테이너에 생성된 프록시를 전달하고 등록한다.
이제 이를 기반으로 다음 포스팅에는 스프링 AOP가 어떻게 동작하는지 살펴보도록 하자.
위 내용은 Inflearn 김영한님의 스프링 핵심원리 고급편 강의를 기반으로 작성되었습니다.
'Backend > Spring' 카테고리의 다른 글
Invalid character found in method name 에러 (2) | 2023.05.07 |
---|---|
Spring AOP (0) | 2023.04.26 |
[Intellij] JUnit Test 시 No tests found for given includes: (0) | 2023.04.22 |
자바 리플렉션과 이를 이용한 JDK 동적 프록시와 CGLIB (0) | 2023.04.20 |
프록시 (0) | 2023.04.18 |