Skip to content

Latest commit

 

History

History
237 lines (177 loc) · 11.1 KB

AOP.md

File metadata and controls

237 lines (177 loc) · 11.1 KB

Aspect Orientied Programming

정의

  • AOP 는 관점 지향 프로그래밍이라고 한다.
  • 관점 지향 프로그래밍의 핵심은 역할책임을 적절하게 분리하는데에 있다.
    • 여러 비지니스 로직에 공통으로 적용되어있는 부가 기능 로직을 따로 분리하여 관리할 수 있다.

ProxyFactory

cglib

  • 스프링은 ProxyFactory 라는 기능을 사용하여 인터페이스 여부에 따라서 JDK Dynamic Proxy 를 쓸지, CGLIB 을 쓸지 정한다.

proxyfactory

  • 부가 기능을 적용하기 위해서 JDK Dynamic Proxy 의 InvocationHandler 와 CGLIB 의 MethodInterceptor 를 따로 만들 필요 없고 Advice 만 만들면 된다.
    • 즉, InvocationHandler 나 MethodInterceptor 는 Advice 를 호출하게 된다.

advice

Advice 만드는 방법

Advice 는 프록시에서 제공할 부가 기능 로직이라고 생각하면 된다.

  • 스프링이 제공하는 MethodInterceptor(CGLIB 과 이름이 동일하다.)
  • MethodInterceptor 는 Interceptor 를 상속하고 Interceptor 는 Advice 인터페이스를 상속한다.
package org.aopalliance.intercept;

public interface MethodInterceptor extends Interceptor {
  Object invoke(MethodInvocation invocation) throws Throwable;
}
  • Advice
@Slf4j
public class TimeAdvice implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        // target 클래스를 호출하고 결과를 받는다
        // ProxyFactory 로 프록시를 생성하는 단계에서 이미 target 정보를 파라미터로 받기 때문에
        // 프록시가 호출할 실제 대상인 target 은 invocation 안에 존재한다.
        Object result = invocation.proceed();

        stopWatch.stop();
        log.info(stopWatch.prettyPrint());

        return result;
    }
}
  • Test
@Slf4j
class ProxyFactoryTest {

    @Test
    void interfaceProxy() {
        ServiceInterface target = new ServiceImpl();
        
        // target 이 인터페이스로 구현되어있으면 JDK Dynamic Proxy 생성, 구체 클래스 기반이면 CGLIB Proxy 생성
        ProxyFactory proxyFactory = new ProxyFactory(target);
        proxyFactory.addAdvice(new TimeAdvice());

        ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();
        log.info("targetClass={}", target.getClass());
        log.info("proxyClass={}", proxy.getClass());

        proxy.save();

        assertThat(AopUtils.isAopProxy(proxy)).isTrue();
        assertThat(AopUtils.isJdkDynamicProxy(proxy)).isTrue();
        assertThat(AopUtils.isCglibProxy(proxy)).isFalse();
    }
}

proxyFactory.setProxyTargetClass(true); 를 적용하면 인터페이스가 있어도 CGLIB 을 사용한다.

Pointcut, Advice, Advisor

  • Pointcut
    • 어디에 부가 기능을 적용할지 판단하는 필터링 로직
    • 주로 클래스와 메서드 이름으로 필터링
  • Advice
    • 프록시가 호출하는 부가 기능 로직(프록시 로직)
  • Advisor
    • Pointcut + Advice 한쌍을 의미
    • Advisor 는 어떤 부가 기능 로직어디에 적용할지를 알고 있다.

AOP 의 핵심은 역할과 책임을 잘 구분하는 것이다.(핵심 기능과 부가 기능을 분리하는 것) Pointcut 은 필터링 로직만 담당하고, Advice 는 부가 기능 로직만 담당한다.

다양한 포인트컷

  • AspectJExpressionPointcut
    • spectJ 표현식으로 매칭한다.
    • 실무에서 가장 많이 사용되는 포인트컷이다.
    • @Pointcut("execution(public * come.weave..repository.*.*(..))")
      public void webLog(){}
      
      @Around("webLog()")
      public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
          // 스톱워치로 시간 측정
          StopWatch stopWatch = new StopWatch();
          stopWatch.start();
          
          // 생략
      }
  • NameMatchMethodPointcut
    • 메서드 이름을 기반으로 매칭한다. 내부에서는 PatternMatchUtils 를 사용한다.
  • JdkRegexpMethodPointcut
    • JDK 정규 표현식을 기반으로 포인트컷을 매칭한다.
  • TruePointcut
    • 항상 참을 반환한다.
  • AnnotationMatchingPointcut
    • 어노테이션으로 매칭한다.

@Aspect

  • 스프링은 @Aspect 어노테이션으로 Advisor 생성을 지원한다.
  • @Aspect 는 관점 지향 프로그래밍(AOP)을 가능하게 하는 AspectJ 프로젝트에서 제공하는 어노테이션이다.
  • @Aspect 를 통해서 스프링이 알아서 프록시를 생성해 주는 것이다.
@Slf4j
@Aspect
public class LogTraceAspect {
 
 private final LogTrace logTrace;
 
 public LogTraceAspect(LogTrace logTrace) {
    this.logTrace = logTrace;
 }
 
 // Advisor
 @Around("execution(* hello.proxy.app..*(..))")
 public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
   TraceStatus status = null;
   
   // log.info("target={}", joinPoint.getTarget()); // 실제 호출 대상
   // log.info("getArgs={}", joinPoint.getArgs()); // 전달인자
   // log.info("getSignature={}", joinPoint.getSignature()); // join point 시그니처
   
   try {
       String message = joinPoint.getSignature().toShortString();
       status = logTrace.begin(message);
       
       // 로직 호출
       Object result = joinPoint.proceed();
       
       logTrace.end(status);
       return result;
     } catch (Exception e) {
       logTrace.exception(status, e);
       throw e;
     }
   }
}
  • @Around 는 target 로직 실행 전, 후에 적용된다.
  • AOP 를 빈으로 등록하기 위해서는 ComponentScan 방법을 쓰거나, 수동 빈으로 등록하면 된다.

advisor

횡단 관심사(cross-cutting concerns)

  • 횡단 관심사(cross-cutting concerns) 는 애플리케이션의 여러 기능들 사이에 적용되는 부가 기능 로직을 말한다.
    • Ex. Log 를 남기는 기능은 3 Layer Architectures 전반에 걸쳐서 동작 되어야 한다.

AOP

  • 스프링은 하나의 프록시에 여러 Advisor 를 적용할 수 있도록 해준다. 따라서, target 에 여러 AOP 가 동시에 적용되더라도, 스프링 AOP 는 target 마다 하나의 프록시만 생성한다.
  • AOP 는 OOP 를 대체하기 위한 것이 아니라 횡단 관심사를 깔끔하게 처리하기 어려운 OOP 의 부족한 부분을 보조하는 목적으로 개발되었다.
  • 실무에서는 AOP 를 구현하기 위해 AspectJ 프레임워크를 주로 사용한다.

@ControllerAdvice

  • @ControllerAdvice 는 특정 컨트롤러에서 발생하는 예외 처리와 같은 작업을 전역적으로 처리할 수 있게 해준다.
    • 즉, @ControllerAdvice 와 @ExceptionHandler 를 통해서, 기존 컨트롤러에서 처리되어야하는 API 예외Advice(부가 기능 로직) 로 판단하여, 별도의 클래스에서 관리할 수 있다.
  • Advice 에서 알 수 있듯이, AOP 를 사용한다.
  • Controller 에서 알 수 있듯이, Controller 를 대상으로 한다.

Why is it called "Controller Advice"?

The term Advice comes from Aspect-Oriented Programming (AOP) which allows us to inject cross-cutting code (called "advice") around existing methods. A controller advice allows us to intercept and modify the return values of controller methods, in our case to handle exceptions.

  • 특정 경로나 어노테이션등을 @ControllerAdvice 속성에 지정할 수 있다.
    • @ControllerAdvice("com.reflectoring.controller")
    • @ControllerAdvice(annotations = Advised.class)

동작 과정

스프링에서 Handler(Controller) 와 HandlerMethod 를 찾는 역할을 DispatcherServlet 을 통해 HandlerMapping, HandlerAdapter 를 통해 진행한다.

  • 에러가 발생하면 DispatcherServlet 의 processHandlerException() 메서드가 동작한다.
    • dispatcherservletex
    • 그리고 HandlerExceptionResolver 에는 아래와 같이 여러 Resolver 들이 담겨있다. 적절한 Resolver 를 선택해서 예외 처리를 하겠구나라는 감이 올 것이다.
    • handlerExceptionResolver
    • exMv = resolver.resolveException(request, response, handler, ex); 를 실행한다.
  • HandlerExceptionResovlerComposite 의 resolveException() 메서드가 동작한다.
    • handlerExceptionResolverComposite
  • AbstractHandlerExceptionResolver 의 resolveException() 메서드가 동작한다.
    • abstractHandlerExResolver
  • AbstractHandleMethodExceptionResolver 의 doResolveException() 메서드가 동작한다.
    • abstractHandlermethodExResolver
  • ExceptionHandlerExceptionResolver 의 doResolveHandlerMethodException() 메서드가 동작한다.
    • exhandlerexresolver
  • ServletInvocableHandlerMethod 의 invokeAndHandle() 메서드가 동작한다.
    • servletInvocable
  • InvocationHandlerMethod 의 doInvoke() 메서드가 동작한다.
    • invocationhandlermethod1
    • invocationhandlermethod2
    • invocationhandlermethod3
    • invocationhandlermethod4

References