[Spring] 빈 후처리기
이전에 작성했던 프록시 팩토리의 문제점을 해결하기 위해서 빈 후처리기를 사용한다고 했다.
프록시 팩토리 내용에 대해 알고싶으면 아래 링크를 참고하면 된다.
https://bsssss.tistory.com/1482
빈 후처리기란
일반적으로 @Bean 이나 @Component 를 사용하면 스프링은 대상 객체를 생성하고
스프링 컨테이너 내부의 빈 저장소에 등록한다.
빈 후처리기는 객체를 조작할 수도 있고, 다른 객체로 바꿔치기 하는 것도 가능하다.
빈 후처리기 - 스프링 빈 등록 과정
- 생성 : 스프링 빈 대상이 되는 객체를 생성(@Bean, 컴포넌트 스캔 포함)
- 전달 : 생성된 객체를 빈 저장소에 등록하기 직전에 빈 후처리기에 전달
- 후 처리 작업 : 빈 후처리기는 전달된 스프링 빈 객체를 조작하거나 다른 객체로 바꿔치기 가능
- 등록 : 빈 후처리기는 빈을 반환, 전달 된 빈을 그대로 반환하면 해당 빈이 등록되고, 바꿔치기 하면 다른 객체가 빈 저장소에 등록됨
예제
A객체만 스프링빈으로 등록한다.
beanA라는 이름으로 A타입의 스프링 빈을 찾을 수 있다.
B타입 객체는 빈으로 등록되지 않았기 때문에 NoSuchBeanDefinitionException으로 예외처리를 해주어야 빌드가 잘 된다.
아래는 빈이 등록된 결과값이다.
빈 후처리기 - 바꿔치기
빈 객체를 다른 객체로 바꿔치기해서 등록할 수도 있다.
그림에서 스프링 빈으로 추가된 것은 A객체이지만, 빈 후처리기가 빈 등록 직전에 바꿔치기해서 B객체를 등록했다.
즉, 실제 객체를 프록시 객체로 바꿔치기해서 등록할 수도 있다.
예제
빈 후처리기는 BeanPostProcessor 인터페이스를 구현해서 스프링 빈으로 등록해서 생성한다.
- postProcessBeforeInitialization() : 객체 생성 후, 초기화 작업 이전에 후처리를 진행
- postProcessAfterInitialization() : 객체 생성 후, 초기화 작업 이후에 후처리를 진행
빈 후처리기를 사용하기 위해 수동으로 빈 등록을 해준다.
이전에 A객체를 선언한 곳에 B를 사용함으로써 객체를 바꿔치기 해준다.
결과를 보면 beanA로 빈을 가져와 실행했지만 출력된 로그는 B 객체로 바꿔치기 된것을 확인할 수 있다.
즉, 스프링 빈 이름에 A 대신에 B객체가 등록되었고 A는 스프링 빈으로 등록조차 되지 않은 것을 확인할 수 있다.
+
일반적으로 컴포넌트 스캔의 대상이 되는 빈들은 중간에 조작할 수 있는 방법이 없지만
빈 후처리기를 사용하면 개발자가 등록하는 모든 빈을 조작할 수 있다.
즉, 빈 객체를 프록시로 교체하는 것도 가능하다는 뜻이다.
빈 후처리기 적용 예제
BeanPostProcessor 를 사용해 빈 후처리기를 생성한다.
advisor 를 주입받아 사용한다.
postProcessAfterInitialization에 파라미터와 빈 이름을 받아서 패키지 이름을 확인하고 basePackage에 해당하면 프록시 객체로 바꿔치기해서 등록한다.
프록시 팩토리에 의해 Advisor만 있으면 프록시 객체를 생성할 수 있다.
프록시 적용 대상 여부 체크
- 애플리케이션을 실행하면 내가 직접 등록한 스프링 빈 뿐만 아니라 스프링 부트가 기본으로 등록하는 수많은 빈들이 빈 후처리기에 넘어온다. 그래서 어떤 빈을 프록시로 만들 것인지 기준이 필요하다.
- 스프링 부트가 기본으로 제공하는 빈 중에는 객체를 만들 수 없는 빈들도 있다. 따라서 모든 객체를 프록시로 만들 경우에는 오류가 발생한다.
+
빈 후처리기 덕분에 프록시를 생성하는 부분은 하나로 집중할 수 있어서 스프링 빈이 추가되어도 프록시와 관련된 코드는 변경하지 않아도 된다.
스프링이 제공하는 빈 후처리기
스프링에서는 다양한 종류의 빈 후처리기를 제공하는데 그 중 프록시 생성을 위한 빈 후처리기도 있다.
먼저 build.gradle 에 라이브러리를 추가해주어야 한다.
implementation 'org.springframework.boot:spring-boot-starter-aop'
해당 라이브러리를 추가하면 aspectJ 관련 라이브러리를 등록하고, 스프링 부트가 AOP 관련 클래스를 자동으로 스프링 빈에 등록한다.
AnnotationAwareAspectJAutoProxyCreator( = AutoProxyCreator)
- 빈 후처리기가 스프링 빈에 자동으로 등록이 된다.
- 이 빈 후처리기는 빈으로 등록된 Advisor 를 자동으로 찾아서 프록시가 필요한 곳에 자동으로 프록시를 적용해준다.
- advisor 에 pointcut 이 포함되어 있기 때문에 Pointcut을 통해 advice가 어디에 적용되어야 하는지 판단할 수 있는 것이다.
- @AspectJ 와 관련된 AOP 기능도 자동으로 찾아서 처리해준다.
자동 프록시 생성 과정
- 생성 : 스프링 빈 대상이 되는 객체를 생성한다.
- 전달 : 생성된 객체를 빈 저장소에 등록하기 직전에 빈 후처리기에 전달한다.
- 모든 Advisor 빈 조회 : 스프링 컨테이너에서 모든 Advisor 를 조회한다.
- 프록시 적용 대상 체크 : 앞서 조회한 Advisor에 포함되어 있는 포인트컷을 사용해서 해당 객체가 프록시를 적용할 대상인지 아닌지 판단한다.
- 프록시 생성 : 프록시 적용 대상이면 프록시를 생성하고 반환해서 프록시를 스프링 빈으로 등록하고, 프록시 적용 대상이 아니라면 원본 객체를 반환해서 원본 객체를 스프링 빈으로 등록한다.
- 빈 등록 : 반환된 객체는 스프링 빈으로 등록된다.
+
참고로 모든 곳에 프록시를 생성하는 것은 비용 낭비이다. 필요한 곳에 최소한의 프록시만 적용해서 사용한다.
자동 프록시 생성기는 모든 스프링 빈에 프록시를 적용하는 것이 아닌 포인트 컷으로 한번 필터링해서 어드바이스가 사용될 가능성이 있는 곳에만 프록시를 생성한다.
예제
@Slf4j
@Configuration
@Import({AppV1Config.class, AppV2Config.class})
public class AutoProxyConfig {
// hello.proxy.app 하위만 프록시, 어드바이스 적용 대상이다. 단 noLog 메소드는 제외이다.
@Bean
public Advisor advisor(LogTrace logTrace) {
// pointcut
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
// hello.proxy.app 하위패키지의 모든 메서드는 포인트컷에 매칭하되, nolog() 메서드는 제외
pointcut.setExpression(
"execution(* hello.proxy.app..*(..)) && !execution(* hello.proxy.app..noLog(..))");
// advice
LogTraceAdvice advice = new LogTraceAdvice(logTrace);
//advisor = pointcut + advice
return new DefaultPointcutAdvisor(pointcut, advice);
}
}