728x90
어드 바이스 생성
public class LogTraceAdvice implements MethodInterceptor {
private final LogTrace logTrace;
public LogTraceAdvice(LogTrace logTrace) {
this.logTrace = logTrace;
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
TraceStatus status = null;
try {
Method method = invocation.getMethod();
String message = method.getDeclaringClass().getSimpleName() + "." + method.getName() + "()";
status = logTrace.begin(message);
//target 호출
Object result = invocation.proceed();
logTrace.end(status);
return result;
}catch (Exception e){
logTrace.exception(status, e);
throw e;
}
}
}
빈 등록
@Slf4j
@Configuration
public class ProxyFactoryConfigV1 {
@Bean
public OrderControllerV1 orderControllerV1(LogTrace logTrace){
OrderControllerV1 orderController = new OrderControllerV1Impl(orderServiceV1(logTrace));
ProxyFactory factory = new ProxyFactory(orderController);
factory.addAdvisor(getAdvisor(logTrace));
OrderControllerV1 proxy = (OrderControllerV1) factory.getProxy();
log.info("ProxyFactory proxy={}, target={}", proxy.getClass(), orderController.getClass());
return proxy;
}
@Bean
public OrderServiceV1 orderServiceV1(LogTrace logTrace){
OrderServiceV1 orderService = new OrderServiceV1Impl(orderRepositoryV1(logTrace));
ProxyFactory factory = new ProxyFactory(orderService);
factory.addAdvisor(getAdvisor(logTrace));
OrderServiceV1 proxy = (OrderServiceV1) factory.getProxy();
log.info("ProxyFactory proxy={}, target={}", proxy.getClass(), orderService.getClass());
return proxy;
}
@Bean
public OrderRepositoryV1 orderRepositoryV1(LogTrace logTrace){
OrderRepositoryV1 orderRepository = new OrderRepositoryV1Impl();
ProxyFactory factory = new ProxyFactory(orderRepository);
factory.addAdvisor(getAdvisor(logTrace));
OrderRepositoryV1 proxy = (OrderRepositoryV1) factory.getProxy();
log.info("ProxyFactory proxy={}, target={}", proxy.getClass(), orderRepository.getClass());
return proxy;
}
private Advisor getAdvisor(LogTrace logTrace){
//포인트컷
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedNames("request*", "order*", "save*");
LogTraceAdvice advice = new LogTraceAdvice(logTrace);
return new DefaultPointcutAdvisor(pointcut, advice);
}
}
- 포인트컷은 NameMatchMethodPointcut 을 사용한다. 여기에는 심플 매칭 기능이 있어서 * 을 매칭할 수 있다.
- request* , order* , save* : request 로 시작하는 메서드에 포인트컷은 true 를 반환한다. 나머지도 같다.
- 이렇게 설정한 이유는 noLog() 메서드에는 어드바이스를 적용하지 않기 위해서다.
- 어드바이저는 포인트컷( NameMatchMethodPointcut ), 어드바이스( LogTraceAdvice )를 가지고 있다.
- 프록시 팩토리에 각각의 target 과 advisor 를 등록해서 프록시를 생성한다. 그리고 생성된 프록시를 스프링 빈으로 등록한다
어플리케이션 실행하기
@Import(ProxyFactoryConfigV1.class)
@SpringBootApplication(scanBasePackages = "hello.proxy.app") //주의
public class ProxyApplication {
public static void main(String[] args) {
SpringApplication.run(ProxyApplication.class, args);
}
@Bean
public LogTrace logTrace(){
return new ThreadLocalLogTrace();
}
}
클래스 기반 프록시 팩토리 생성
빈 등록
@Slf4j
@Configuration
public class ProxyFactoryConfigV2 {
@Bean
public OrderControllerV2 orderControllerV2(LogTrace logTrace){
OrderControllerV2 orderController = new OrderControllerV2(orderServiceV2(logTrace));
ProxyFactory factory = new ProxyFactory(orderController);
factory.addAdvisor(getAdvisor(logTrace));
OrderControllerV2 proxy = (OrderControllerV2) factory.getProxy();
log.info("ProxyFactory proxy={}, target={}", proxy.getClass(), orderController.getClass());
return proxy;
}
@Bean
public OrderServiceV2 orderServiceV2(LogTrace logTrace){
OrderServiceV2 orderService = new OrderServiceV2(orderRepositoryV2(logTrace));
ProxyFactory factory = new ProxyFactory(orderService);
factory.addAdvisor(getAdvisor(logTrace));
OrderServiceV2 proxy = (OrderServiceV2) factory.getProxy();
log.info("ProxyFactory proxy={}, target={}", proxy.getClass(), orderService.getClass());
return proxy;
}
@Bean
public OrderRepositoryV2 orderRepositoryV2(LogTrace logTrace){
OrderRepositoryV2 orderRepository = new OrderRepositoryV2();
ProxyFactory factory = new ProxyFactory(orderRepository);
factory.addAdvisor(getAdvisor(logTrace));
OrderRepositoryV2 proxy = (OrderRepositoryV2) factory.getProxy();
log.info("ProxyFactory proxy={}, target={}", proxy.getClass(), orderRepository.getClass());
return proxy;
}
private Advisor getAdvisor(LogTrace logTrace){
//포인트컷
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedNames("request*", "order*", "save*");
LogTraceAdvice advice = new LogTraceAdvice(logTrace);
return new DefaultPointcutAdvisor(pointcut, advice);
}
}
어플리케이션 실행하기
@Import(ProxyFactoryConfigV2.class)
@SpringBootApplication(scanBasePackages = "hello.proxy.app") //주의
public class ProxyApplication {
public static void main(String[] args) {
SpringApplication.run(ProxyApplication.class, args);
}
@Bean
public LogTrace logTrace(){
return new ThreadLocalLogTrace();
}
}
정리
프록시 팩토리 덕분에 개발자는 매우 편리하게 프록시를 생성할 수 있게 되었다. 추가로 어드바이저, 어드바이스, 포인트컷 이라는 개념 덕분에 어떤 부가 기능을 어디에 적용할 지 명확하게 이해할 수 있었다.
남은 문제
문제1 - 너무 많은 설정
바로 ProxyFactoryConfigV1 , ProxyFactoryConfigV2 와 같은 설정 파일이 지나치게 많다는 점이다. 예를 들어서 애플리케이션에 스프링 빈이 100개가 있다면 여기에 프록시를 통해 부가 기능을 적용하려면 100개의 동적 프록시 생성 코드를 만들어야 한다! 무수히 많은 설정 파일 때문에 설정 지옥을 경험하게 될 것이다. 최근에는 스프링 빈을 등록하기 귀찮아서 컴포넌트 스캔까지 사용하는데, 이렇게 직접 등록하는 것도 모자라서, 프록시를 적용하는 코드까지 빈 생성 코드에 넣어야 한다.
문제2 - 컴포넌트 스캔
애플리케이션 V3처럼 컴포넌트 스캔을 사용하는 경우 지금까지 학습한 방법으로는 프록시 적용이 불가능하다. 왜냐하면 실제 객체를 컴포넌트 스캔으로 스프링 컨테이너에 스프링 빈으로 등록을 다 해버린 상태이기 때문이다. 지금까지 학습한 프록시를 적용하려면, 실제 객체를 스프링 컨테이너에 빈으로 등록하는 것이 아니라 ProxyFactoryConfigV1 에서 한 것 처럼, 부가 기능이 있는 프록시를 실제 객체 대신 스프링 컨테이너에 빈으로 등록해야 한다
728x90
'스프링 핵심 원리(고급편)' 카테고리의 다른 글
| Ch07. 빈 후처리기 - 빈 후처리기(예제) (0) | 2022.04.13 |
|---|---|
| Ch07. 빈 후처리기 - 빈 후처리기 (0) | 2022.04.13 |
| Ch06. 스프링이 지원하는 프록시 - 어드바이저(예제) (0) | 2022.04.12 |
| Ch06. 스프링이 지원하는 프록시 - 포인트컷, 어드바이스, 어드바이저( 소개) (0) | 2022.04.12 |
| Ch06. 스프링이 지원하는 프록시 - 프록시 팩토리(예제) (0) | 2022.04.12 |