@Aspect를 알아보자

Drumj·2023년 2월 15일
0

오늘의 학습

오늘은 AOP의 구현 파트를 수강 했다.

@Aspect와 관련해서 여러가지를 배울 수 있는 강의였다.
@Aspect가 붙은 클래스 안에는 포인트 컷과 어드바이스가 존재하고
이것을 알아서 어드바이저로 만들어서 AOP 기능을 수행해준다.

우선 순차적으로 정리해보자!


1. 가장 간단한 Aspect

@Slf4j
@Aspect
public class AspectV1 {

    //hello.aop.order 패키지와 하위 패키지
    @Around("execution(* hello.aop.order..*(..))")
    public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("[log] {}", joinPoint.getSignature()); //join point 시그니처
        return joinPoint.proceed();
    }
}

@Around 가 포인트컷이 되고 어떤 녀석들에 적용할 지 지정해준다. 표현식은 AspectJ의 표현식을 사용.
doLog() 가 어드바이스가 된다. 어떤 동작을 수행할 지 지정.
@Around는 반드시 joinpoint.proceed()를 수행해야 한다. 뒤에 더 자세히 설명하겠다.


2. 포인트컷 분리

@Slf4j
@Aspect
public class AspectV2 {
    //포인트컷 분리

    //hello.aop.order 패키지와 하위 패키지
    @Pointcut("execution(* hello.aop.order..*(..))")
    private void allOrder() {} //pointcut signature

    @Around("allOrder()")
    public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("[log] {}", joinPoint.getSignature()); //join point 시그니처
        return joinPoint.proceed();
    }
}

@Pointcut을 사용해서 분리 할 수 있다. 이때 publlic으로 지정하면 다른 곳에서도 참조 할 수 있다. (이후 포인트컷 외부 참조에서 다룸)

@Around에는 @Pointcut의 메소드 이름을 적어주면 된다.


3. 여러 개의 포인트컷과 어드바이스

@Slf4j
@Aspect
public class AspectV3 {
    //포인트컷 분리, 여러개 사용

    //hello.aop.order 패키지와 하위 패키지
    @Pointcut("execution(* hello.aop.order..*(..))")
    private void allOrder() {} //pointcut signature

    //클래스 이름 패턴이 *Service
    @Pointcut("execution(* *..*Service.*(..))")
    private void allService() {}

    @Around("allOrder()")
    public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("[log] {}", joinPoint.getSignature()); //join point 시그니처
        return joinPoint.proceed();
    }

    //hello.aop.order 패키지와 하위 패키지 이면서 클래스 이름 패턴이 *Service
    @Around("allOrder() && allService()")
    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());
        }
    }
}

@Pointcut@Around를 여러개 사용할 수도 있다.
이 코드에서부터는 트랜잭션의 동작을 간단하게 로그만 남겨서 알아보았다. 이 코드를 토대로 이후에 어드바이스의 여러 종류에 대해 알아볼 수 있다.


4.포인트컷 외부 참조

위의 코드에 있는 @Pointcut을 따로 클래스를 만들어 놔두고 참조해서 사용해 보자!

//포인트컷 클래스
public class Pointcuts {

    //hello.aop.order 패키지와 하위 패키지
    @Pointcut("execution(* hello.aop.order..*(..))")
    public void allOrder() {} //pointcut signature

    //클래스 이름 패턴이 *Service
    @Pointcut("execution(* *..*Service.*(..))")
    public void allService() {}

    //allOrder && allService
    @Pointcut("allOrder() && allService()")
    public void orderAndService() {}
}

//Aspect 클래스
@Slf4j
@Aspect
public class AspectV4Pointcut {
    //포인트컷 참조

    @Around("hello.aop.order.aop.Pointcuts.allOrder()")
    public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("[log] {}", joinPoint.getSignature()); //join point 시그니처
        return joinPoint.proceed();
    }

    //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());
        }
    }
}

이렇게 외부 클래스의 포인트컷을 참조하기 위해서는 우선 @Pointcutpublic으로 만들어야 하고
Aspect 클래스에서는 @Around패키지명.클래스명.메소드명()으로 받아오면 된다.


5. 여러 어드바이스의 순서를 지정해보자

하나의 Aspect 안에서는 순서를 보장하지 않는다.
그래서 따로 클래스를 분리해서 @Order를 사용해서 순서를 지정할 수 있다.
따로 클래스를 만들어야 하지만 해당 예제에서는 static class를 사용해서 했다.

@Slf4j
public class AspectV5Order {
    //어드바이스 순서를 지정하기 위해 @Order 사용.
    //@Order 를 사용하기 위해 클래스 단위로 분할

    @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()); //join point 시그니처
            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());
            }
        }
    }
}

다음으로

우선 간단하게 여기까지만 정리하고 마지막 남은 어드바이스의 여러 종류는 따로 정리하도록 하겠다.

0개의 댓글