-
어드바이저의 실제 적용스프링/스프링 AOP 2023. 9. 17. 18:01
테스크 코드에서의 예제가 아니라 실제 적용하게 된다면 어떻게 동작하는지에 대한 포스팅이다.
실제 사용하는 어드바이스 - LogTraceAdvice.class
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); //로직 호출 Object result = invocation.proceed(); logTrace.end(status); return result; } catch (Exception e) { logTrace.exception(status, e); throw e; } } }
Methodinterceptor를 상속받아서 invoke를 오버라이드하는 과정이다.
main.class에 설정 추가
@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(); } }
logTrace를 사용하고, Configuration을 proxyFactoryConfigV1만 사용하기 위해 메인 클래스에 특정 설정을 추가해준다.
1. 인터페이스일 경우
@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) { OrderRepositoryV1Impl 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) { // pointcut NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut(); pointcut.setMappedNames("request*", "order*", "save*"); LogTraceAdvice advice = new LogTraceAdvice(logTrace); return new DefaultPointcutAdvisor(pointcut, advice); } }
스프링 빈으로 설정하는 과정인데, 일단 pointcut을 설정한 뒤에 DefaultPointCutAdvisor를 반환하는 getAdvisor()를 선언한다. 그 뒤에 repository, service, controller 계층에서 의존관계 주입을 하며 프록시를 호출해준다. 코드가 길긴하지만 프록시 팩토리를 선언한 다음에 리턴해주는 것은 반복이니까 크게 추가된 점은 없다.
2. 구체 클래스일 경우
이 경우에도 코드는 위와 같다. 단지, V1 버전을 V2로 바꿔주면 된다. 그 이유는 저번 포스팅에 말했던 것처럼 스프링에서 지원하는 프록시 팩토리는 인터페이스의 여부에 따라 각각에 맞는 프록시를 동적으로 선택해주기 때문이다.
이 경우에는 자동으로 CGLIB로 프록시가 생성되기 때문에 위의 코드에서 수정할 것이 없다.
정리
프록시 팩토리와 어드바이저, 어드바이스, 포인트컷이라는 개념 덕분에 어떤 부가 기능을 어디에 적용할 지 명확하게 적용할 수 있었다.
여기에도 몇 가지 단점이 있는데, 설정이 너무 많다는 문제점과 컴포넌트 스캔의 단점이다.
스프링 빈을 등록하는 것도 컴포넌트 스캔으로 하는데, configuration으로 빈을 등록하는 것도 모자라서 프록시를 적용하는 코드까지 빈 생성 코드에 넣어야한다.
그리고 컴포넌트 스캔을 사용할 경우에는 이와 같은 프록시 적용이 불가능한데, 이러한 문제점을 해결해주는 것이 빈 후처리기이고, 다음 포스팅에서 설명할 예정이다.
'스프링 > 스프링 AOP' 카테고리의 다른 글
@Aspect 프록시 (0) 2023.09.20 빈 후처리기 (0) 2023.09.17 여러 어드바이저 동시 적용 (0) 2023.09.17 포인트컷, 어드바이스, 어드바이저 (0) 2023.09.17 프록시 팩토리(ProxyFactory) (0) 2023.09.17