스프링에서 제공하는 AOP 기능

Hyunta·2022년 12월 12일
0
post-custom-banner

스프링은 @Aspect 애노테이션을 통해서 편리하게 AOP 기능을 사용할 수 있도록 구현해놨다. 직접 Aspect를 사용해보면서 다양한 사용 방법을 정리해보려고 한다.

Aspect 구현

가장 먼저 메서드가 실행될 때 로그를 출력하는 기능을 구현해보자.

@Slf4j
@Aspect
public class AspectV1 {

    @Around("execution(* hello.aop.order..*(..))")
    public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("[log] {}", joinPoint.getSignature()); // joinPoint 시그니처
        return joinPoint.proceed();
    }
}
  1. @Aspect를 통해 Advice로 등록한다.
  2. @Around를 이용해서 해당 기능을 적용할 포인트컷을 지정한다.

해당 Flow는 아래 그림처럼 흘러간다.

Pointcut 분리

@Around 에 직접 주입한 포인트컷은 메서드로 분리할 수도 있고, 외부 클래스에서 참조하는 방식으로도 변경가능하다.


1. 같은 클래스의 메서드로 분리
    @Pointcut("execution(* hello.aop.order..*(..))")
    private void allOrder() { //pointcut signature
    }

2. 외부 클래스에서 메서드로 분리
public class PointCuts {
    @Pointcut("execution(* hello.aop.order..*(..))")
    public void allOrder() { //pointcut signature
    }
}

포인트 컷을 구현할 때 고려해야할 사항이 몇가지 있다.

  1. 메서드 이름과 파라미터를 합쳐서 포인트컷의 시그니처라 한다.
  2. 메서드의 반환 타입은 void여야 한다.
  3. 코드 내용은 비워둔다.
  4. 메서드 이름을 통해서 의미를 전달할 수 있다.

하나의 Advice에 여러 Pointcut 조건 제시

하나의 어드바이스에 여러개의 포인트 컷을 부여할 수도 있다.

    @Around("allOrder() && allService()")
    public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable {

&&, ||, ! 등을 통해서 조건을 제시할 수 있다. 작성한 코드는 Order 패키지에 있는 Service 클래스들에 트랜잭션을 붙이는 로직의 애노테이션이다.

현재 Order에 있는 Service에는 doLog와 doTransaction 어드바이스 두개가 존재한다. 순서는 기본적으로 작성된 순서에 의존하는데 순서를 변경하고 싶으면 클래스를 통해서 분리해야한다.

여러 Pointcut에 우선순위 제시하기

@Slf4j
public class AspectV5Order {

    @Aspect
    @Order(2)
    public static class LogAspect {
        @Around("hello.aop.order.aop.PointCuts.allOrder()")
        public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
            log.info("[log] {}", joinPoint.getSignature()); // joinPoint 시그니처
            return joinPoint.proceed();
        }
    }

    @Aspect
    @Order(1)
    public static class TxAspect {
        //hello.aop.order 패키지와 하위 패키지 이면서 클래스 이름 패턴이 *Service
        @Around("hello.aop.order.aop.PointCuts.orderAndService()")
        public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
            try {
                log.info("[트랜잭션 시작] {}", joinPoint.getSignature());
                Object result = joinPoint.proceed();
                log.info("[트랜잭션 커밋] {}", joinPoint.getSignature());
                return result;
            } catch (Exception e) {
                log.info("[트랜잭션 롤백] {}", joinPoint.getSignature());
                throw e;
            } finally {
                log.info("[리소스 릴리즈] {}", joinPoint.getSignature());
            }
        }
    }
}

만약 어드바이스에 우선순위를 부여하고 싶다고 내부 클래스로 관리하던가, 클래스에서 우선순위를 관리해야한다. 만약 메서드에 @Order 애노테이션을 작성하면 우선순위가 부여되지 않는다.

Advice 종류

지금까지 사용한 @Around를 제외하고
@Before, @AfterReturning, @AfterThrowing, @After 등이 있다.

    @Around("hello.aop.order.aop.PointCuts.orderAndService()")
    public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            //@Before
            log.info("[트랜잭션 시작] {}", joinPoint.getSignature());
            
            Object result = joinPoint.proceed();
            
            //@AfterReturning
            log.info("[트랜잭션 커밋] {}", joinPoint.getSignature());
            
            return result;
        } catch (Exception e) {
            //@AfterThrowing
            log.info("[트랜잭션 롤백] {}", joinPoint.getSignature());
            
            throw e;
        } finally {
            //@After
            log.info("[리소스 릴리즈] {}", joinPoint.getSignature());
        }
    }

각각의 시점은 해당 메서드에서 찾아볼 수 있다.
어떻게 생각하면 @Around만 있으면 될 것 같은데 세세한 포인트가 꼭 필요할까 의문을 가질 수 있다. 먼저 각각의 포인트가 필요한 이유는 파라미터에서 찾아볼 수 있다.
@Around의 경우 ProceedingJoinpoint를 받기 때문에 proceed가 꼭 존재해야한다. 만약 proceed가 없다면 핵심 기능이 작동하지 않는다. 이러한 실수를 방지하기 위해서 각 포인트별로 어드바이스가 있도록 구성되어있다.

profile
세상을 아름답게!
post-custom-banner

0개의 댓글