강의 링크는 아래와 같습니다.
스프링 빈 후처리기
필자는 이전에 스프링 기초를 학습하면서 스프링 컨테이너에 빈이 등록될 때 순수한 빈 인스턴스가 아니라,
CGLIB를 사용해 순수한 빈 클래스를 상속한 프록시 객체를 스프링 컨테이너에 등록되는 것을 확인했다.
우리는 빈으로 단순한 클래스를 등록했는데 어떻게 프록시 객체가 들어간 것일까?
이번 파트를 학습하면서 이에 대한 의문점이 풀린 것 같다. 바로 빈 후처리기다. BeanPostProcessor이라고 한다.
말 그대로 빈을 생성한 후에 무언가를 처리하는 용도로 사용한다.
이 기능을 사용해 객체를 바꿔치기한 것이다.
빈 후처리기 실습 정리
- 빈 후처리기를 사용하려면 BeanPostProcessor 인터페이스를 구현하고, 스프링 빈으로 등록해야 한다.
- 이 인터페이스에는 2가지 메서드가 있다. 바로 postProcessBeforeInitialization과 postProcessAfterInitialization이다.
- postProcessBeforeInitialization은 객체 생성 이후에 @PostConstruct 같은 초기화가 발생하기 전에 호출되는 포스트 프로세서다.
- postProcessAfterInitialization은 객체 생성 이후에 @PostConstruct 같은 초기화가 발생한 다음에 호출되는 포스트 프로세서다.
- 아래는 인스턴스를 바꿔치기한 예시 코드다.
@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;
}
}
빈 후초리기는 빈을 조작하고 변경할 수 있는 후킹 포인트다.
빈 객체를 조작하고 다른 객체로 바꿀 수도 있다. 이 방법을 통해 빈 객체를 프록시 객체로 교체한 것이다.
빈 객체를 조작한다는 의미는 해당 객체의 특정 메서드를 호출하는 것이다.
참고로 @PostConstruct 는 스프링 빈 생성 이후에 빈을 초기화 하는 역할을 한다. 그런데 생각해보면 빈의 초기화라는 것이 단순히 @PostConstruct 애노테이션이 붙은 초기화 메서드를 한번 호출만 하면 된다. 쉽게 이야기해서 생성된 빈을 한번 조작하는 것이다.
따라서 빈을 조작하는 행위를 하는 적절한 빈 후처리기가 있으면 될 것 같다.
스프링은 CommonAnnotationBeanPostProcessor 라는 빈 후처리기를 자동으로 등록하는데,
여기에서 @PostConstruct 애노테이션이 붙은 메서드를 호출한다.
따라서 스프링 스스로도 스프링 내부의 기능을 확장하기 위해 빈 후처리기를 사용한다. 흥미로운 이야기다.
실습을 통해서 빈 후처리기에서 빈으로 등록한 객체를 프록시 객체로 바꿔치기 해보았다. 프록시 팩토리를 사용했다.
다음으로는 빈 후처리기를 스프링 빈으로 등록했다. 빈 후처리기는 스프링 빈으로 등록하면 자동으로 동작한다!!!
이제 여기에 프록시에 적용할 패키지 정보와 어드바이저를 넘겨준다.
그러면 빈 후처리기를 통해 원본 객체 대신에 프록시 객체를 반환한다.
이렇게 빈 후처리기를 실습 코드에 적용해봤다.
이 방식으로 통해 프록시를 생성하는 코드가 @Configuration 애노테이션이 붙은 설정 파일에서 없어졌다.
매우 코드가 깔끔해졌지만, 여전히 컴포넌트 스캔과 같은 빈 등록은 현재 방식으로는 프록시를 적용할 수 없고 @Import문을 반복해야하는 문제가 있었다. 이에 대한 해결책을 학습해보자.
위에서 빈 후처리기에 패키지 정보와 어드바이저를 넘겨주었다고 했다.
패키지를 넘긴 것은 프록시의 적용 대상 여부를 패키지를 기준으로 설정했기 때문이다.
그러나 포인트컷을 사용하면 더 깔끔하다.
포인트컷은 이미 클래스, 메서드 단위의 필터 기능을 가지고 있기 때문에, 프록시 적용 대상 여부를 정밀하게 설정할 수 있다.
어드바이저는 포인트컷 + 어드바이스 조합이므로 어드바이저를 사용하면 될 것 같다.
참고로 스프링 AOP는 포인트컷을 사용해 프록시 적용 대상 여부를 체크한다고 한다.
결과적으로 포인트컷은 다음 두 곳에 사용된다.
- 프록시 적용 대상 여부를 체크해서 꼭 필요한 곳에만 프록시를 적용한다. (빈 후처리기 - 자동 프록시 생성)
- 프록시의 어떤 메서드가 호출 되었을 때 어드바이스를 적용할 지 판단한다. (프록시 내부)
여기까지는 빈 후처리기를 직접 만들어서 실습한 예제였고 이번에는 스프링이 제공하는 빈 후처리기에 대해 알아보자.
스프링이 제공하는 빈 후처리기
제일 먼저 아래와 같은 의존성을 build.gradle에 추가한다.
implementation 'org.springframework.boot:spring-boot-starter-aop'
이 라이브러리를 추가하면 aspectjweaver 라는 aspectJ 관련 라이브러리를 등록하고,
스프링 부트가 AOP 관련 클래스를 자동으로 스프링 빈에 등록한다.
AutoProxyCreateor 자동 프록시 생성기
- 앞서 이야기한 스프링 부트 자동 설정으로 AnnotationAwareAspectJAutoProxyCreator 라는
빈 후처리기가 스프링 빈에 자동으로 등록된다. - 이름 그대로 자동으로 프록시를 생성해주는 빈 후처리기이다.
- 이 빈 후처리기는 스프링 빈으로 등록된 Advisor 들을 자동으로 찾아서 프록시가 필요한 곳에 자동으로 프록시를 적용해준다.
- Advisor 안에는 Pointcut 과 Advice 가 이미 모두 포함되어 있다.
따라서 Advisor만 알고 있으면 그 안에 있는 Pointcut으로 어떤 스프링 빈에 프록시를 적용해야할 지 알 수 있다.
그리고 Advice로 부가 기능을 적용하면 된다.
자동 프록시 생성기의 작동 과정은 아래와 같다.
- 생성: 스프링이 스피링 빈 대상이 되는 객체를 생성한다.
- 전달: 생성된 객체를 빈 저장소에 등록하기 직전에 빈 후처리기에 전달한다.
- 모든 advisor 조회: 자동 프록시 생성기 - 빈 후처리기는 스프링 컨테이너에서 모든 advisor를 조회한다.
- 프록시 적용 대상 체크: 앞서 조회한 advisor에 포함되어 있는 포인트컷을 사용해서 해당 객체가 프록시를 적용할 대상인지 아닌지 판단한다. 이때 객체의 클래스 정보는 물론이고, 해당 객체의 모든 메서드를 포인트컷에 하나하나 모두 매칭해본다. 그래서 조건이 하나라도 만족하면 프록시 적용 대상이 된다. 예를 들어서, 10개의 메서드 중에 하나만 포인트컷 조건에 만족해도 프록시 적용 대상이 된다.
- 프록시 생성: 프록시 적용 대상이면 프록시를 생성하고 반환해서 프록시를 스프링 빈으로 등록한다. 만약 프록시 적용 대상이 아니라면 원본 객체를 반환해서 원본 객체를 스프링 빈으로 등록한다.
- 빈 등록: 반환된 객체는 스프링 빈으로 등록된다.
예시 코드는 아래와 같다.
@Configuration
@Import({AppV1Config.class, AppV2Config.class})
public class AutoProxyConfig {
@Bean
public Advisor advisor1(LogTrace logTrace) {
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedNames("request*", "order*", "save*");
LogTraceAdvice advice = new LogTraceAdvice(logTrace);
//advisor = pointcut + advice
return new DefaultPointcutAdvisor(pointcut, advice);
}
}
- AutoProxyConfig 코드를 보면 advisor1 이라는 어드바이저 하나만 등록했다.
- 빈 후처리기는 이제 등록하지 않아도 된다. 스프링은 자동 프록시 생성기라는
( AnnotationAwareAspectJAutoProxyCreator ) 빈 후처리기를 자동으로 등록해준다.
앞에서도 언급했지만 포인트컷의 2가지 역할은 매우 중요하다!!!!
- 프록시 적용 여부 단계 - 생성 단계
- 어드바이스 적용 여부 판단 - 사용 단계
참고로 프록시를 모든 곳에 생성하는 것은 비용 낭비이다. 꼭 필요한 곳에 최소한의 프록시를 적용해야 한다.
그래서 자동 프록시 생성기는 모든 스프링 빈에 프록시를 적용하는 것이 아니라 포인트컷으로 한번 필터링해서 어드바이스가 사용될 가능성이 있는 곳에만 프록시를 생성한다.
AspectJExpressionPointcut
AspectJ라는 AOP에 특화된 포인트컷 표현식을 적용할 수 있다.
예시 코드는 다음과 같다.
@Bean
public Advisor advisor2(LogTrace logTrace) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* hello.proxy.app..*(..))");
LogTraceAdvice advice = new LogTraceAdvice(logTrace);
//advisor = pointcut + advice
return new DefaultPointcutAdvisor(pointcut, advice);
}
execution(* hello.proxy.app..*(..)): 해당 부분이 AspectJ가 제공하는 포인트컷 표현식이다.
AspectJ 포인트컷 표현식과 AOP는 추후에 포스팅에서 다루겠다.
마지막으로 하나만 기억하자
예를 들어서 어떤 스프링빈이 advisor1, advisor2가 제공하는 포인트컷의 조건을 모두 만족하면,
프록시 자동 생성기는 프록시를 몇 개 생성할까??
저번 시간에 학습했지만 당연히 프록시 1개를 생성하고 여러 어드바이저를 프록시 내부에 포함한다.
정리한 포인트컷에 따른 예시 생성 로직은 아래와 같다.
- advisor1 의 포인트컷만 만족 프록시1개 생성, 프록시에 advisor1 만 포함
- advisor1 , advisor2 의 포인트컷을 모두 만족 프록시1개 생성, 프록시에 advisor1 , advisor2 모두 포함
- advisor1 , advisor2 의 포인트컷을 모두 만족하지 않음 프록시가 생성되지 않음
정리
자동 프록시 생성기인 AnnotationAwareAspectJAutoProxyCreator 덕분에 매우 편리하게 프록시를 적용할 수 있다.
Advisor 만 스프링 빈으로 등록하면 된다!!!!
이상으로 포스팅을 마칩니다. 감사합니다.
댓글