Open cme10575 opened 1 year ago
BeanPostProcessor 인터페이스를 구현하고, 스프링 빈으로 등록하면 됨
public interface BeanPostProcessor {
// 객채 생성 이후에 @PostConstruct 같은 초기화가 발생하기 전에 호출되는 포스트 프로세서
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException
// 객채 생성 이후에 @PostConstruct 같은 초기화가 발생한 다음에 호출되는 포스트 프로세서
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
}
예시 - beanA를 A객체 대신에 B객체로 변경하는 빈 후처리기
@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;
}
}
빈 후처리기 클래스
@Slf4j
public class PackageLogTracePostProcessor implements BeanPostProcessor {
private final String basePackage;
private final Advisor advisor;
public PackageLogTracePostProcessor(String basePackage, Advisor advisor) {
this.basePackage = basePackage;
this.advisor = advisor;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
log.info("param beanNAme={] bean={}", beanName, bean.getClass());
// 프록시 적용 대상 여부 체크
// 프록시 적용 대상이 아니면 원본을 그대로 진행
String packageName = bean.getClass().getPackageName();
if (!packageName.startsWith(basePackage)) {
return bean;
}
// 프록시 대상이면 프록시를 만들어서 반환
ProxyFactory proxyFactory = new ProxyFactory(bean);
proxyFactory.addAdvisor(advisor);
Object proxy = proxyFactory.getProxy();
log.info("create proxy: target={} proxy={}", bean.getClass(), proxy.getClass());
return proxy;
}
}
빈 후처리기 빈으로 등록하는 config 클래스
@Slf4j
@Configuration
@Import({AppV1Config.class, AppV2Config.class})
public class BeanPostProcessorConfig {
@Bean
public PackageLogTracePostProcessor logTracePostProcessor(LogTrace logTrace) {
return new PackageLogTracePostProcessor("hello.proxy.app", getAdvisor(logTrace));
}
private Advisor getAdvisor(LogTrace logTrace) {
// pointcut
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedNames("request*", "order*", "save*");
// advice
LogTraceAdvice advice = new LogTraceAdvice(logTrace);
return new DefaultPointcutAdvisor(pointcut, advice);
}
}
이제 로그 찍는 프록시를 생성하는 코드가 설정 파일에는 필요 없다. 프록시를 생성하고 프록시를 스프링 빈으로 등록하는 것은 빈 후처리기가 모두 처리해준다.
스프링이 제공하는 빈 후처리기를 사용하려면 다음을 꼭 추가해주어야 한다.
implementation 'org.springframework.boot:spring-boot-starter-aop'
AnnotationAwareAspectJAutoProxyCreator
라는 자동으로 프록시를 생성해주는 빈 후처리기가 스프링 빈에 자동 등록된다.
이 빈 후처리기는 스프링 빈으로 등록된 Advisor
를 찾아 자동으로 프록시를 적용한다. 왜냐면 Advisor
안에 pointcut
(적용 범위)와 advice
(프록시 기능)이 모두 포함되어 있기 때문에 자동으로 적용할 수 있다.
여러 Advisor의 포인트컷을 모두 만족하면 프록시를 한 개 생성하고, 프록시에 만족하는 Advisor를 모두 포함시킨다.
@AspectJ
와 관련된 AOP 기능도 자동으로 찾아서 처리해준다. (뒤에서 설명)
예제
@Slf4j
@Import({AppV1Config.class, AppV2Config.class})
public class AutoProxyConfig {
@Bean
public Advisor advisor2(LogTrace logTrace) {
// pointcut
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* hello.proxy.app..*(..))");
// advice
LogTraceAdvice advice = new LogTraceAdvice(logTrace);
return new DefaultPointcutAdvisor(pointcut, advice);
}
}
AController
에는 request()
, noLog()
메서드가 있는데 여기서 reqeust()
가 포인트컷 조건에 만족하므로 `AController
는 프록시를 생성한다.AController
에서 request()
메서드는 포인트컷 조건에 만족하므로 프록시는 어드바이스를 먼저 호출하고, target
을 호출한다. AController
에서 noLog()
메서드는 포인트컷 조건에 만족하지 않으므로 프록시는 어드바이스를 호출하지 않고, target
만 호출한다.AspectJ라는 AOP에 특화된 포인트컷 표현식을 이용한 스프링이 제공하는 매우 정밀한 포인트컷
실무에서 실제로 이것만 사용
As-is
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedNames("request*", "order*", "save*");
To-be
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* hello.proxy.app..*(..)) && !execution(* hello.proxy.app..noLog(..))");
스프링에서도(스프링부트x) aspectj를 사용해 aop를 어노테이션으로 설정 가능하다. 참고: https://heidi-mood.com/80
빈 후처리기 (BeanPostProcessor)
엄청 많은 빈이 등록되기 때문에 패키지로 프록시로 감쌀 빈을 제한해야한다. 포인트컷을 사용하면 더 효율적일 것 스프링 AOP는 포인트컷을 사용해 프록시 대상 여부 체크
스프링이 제공하는 빈 후처리기
AutoProxyCreator (AnnotationAwareAspectJAutoProxyCreator)
포인트컷의 사용처
AspectJExpressionPointcut