스프링/스프링 AOP

어드바이스 종류

chanhee01 2023. 9. 22. 20:55
  1. @Around : 메서드 호출 전후에 수행, 조인 포인트 실행 여부 선택, 반환 값 변환 ,예외 변환 등 가능
  2. @Before : 조인 포인트 실행 이전에 실행
  3. @AfterReturning : 조인 포인트가 정상 완료후 실행
  4. @AfterThrowing : 메서드가 예외를 던지는 경우 실행
  5. @After : 조인 포인트가 정상 또는 예외에 관계없이 실행(finally)

 

 

2. @Before

@Before("hello.aop.order.aop.Pointcuts.orderAndService()")
public void doBefore(JoinPoint joinPoint) {
    log.info("[before] {}", joinPoint.getSignature());
}

@Before 어노테이션은 조인 포인트 실행 이전에 실행이 된다. Service 계층에만 조인 포인트를 걸었으니, 로직을 실행하게 된다면 hello가 출력되고 orderService -> orderRepository 순으로 실행될 것이다.

 

여기서 주목해야 할 점은 이전의 엄청 긴 로직에 비해 한줄만 로직에 들어간다는 것이다. 포인트 컷을 걸어두었던 로직이 실행되기 전에 실행되는 것이기 때문에 proceed()를 하거나 다른 로직이 포함되지 않아도 된다.

 

@Around는 ProceedingJoinPoint.proceed()를 호출해야만 다음 대상이 호출되는 반면 @Before는 ProceedingJoinPoint.proceed() 자체를 사용하지 않고, 예외가 발생하지 않는 한 메서드 종료시 자동으로 다음 타겟이 호출된다.

 

 

 

3. @AfterReturning

@AfterReturning(value = "hello.aop.order.aop.Pointcuts.orderAndService()", returning = "result")
public void doReturn(JoinPoint joinPoint, Object result) {
    log.info("[return] {} return={}", joinPoint.getSignature(), result);
}

정상 실행되었을 때 result를 return 해주는 것이다. returning 속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 일치해야한다. 매개변수의 Object result에서 반환타입인 Object는 되도록 Object로 하는것이 좋다. 왜냐하면 메서드의 반환 타입과 같아야하는데, Object는 모두 반환할 수 있기 때문이다.

 

 

4. @AfterThrowing

@AfterThrowing(value = "hello.aop.order.aop.Pointcuts.orderAndService()", throwing = "ex")
public void doThrowing(JoinPoint joinPoint, Exception ex) {
    log.info("[ex] {} message={}", ex);
}

예외가 발생했을 시 예외를 throw 해준다. 예외도 마찬가지로 부모 타입을 지정하면 모든 자식 타입은 인정되므로 Exceptino을 반환해주는 것이 좋다.

 

 

5. @After

@After(value = "hello.aop.order.aop.Pointcuts.orderAndService()")
public void doAfter(JoinPoint joinPoint) {
    log.info("[after] {}", joinPoint.getSignature());
}

try - catch 문의 finally 같은 역할을 한다. 정상 및 예외 반환 조건을 모두 처리한다.

 

 

 

 

크게 어렵거나 복잡한 내용이 없다. 왜냐하면 @Around에서 이미 다 했던 역할이기 때문이다.

@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());
        return e;
    } finally {
        // @After
        log.info("[리소스 릴리스] {}", joinPoint.getSignature());
    }
}

각 주석이 있는 곳에서 역할을 수행하는 것뿐이다.

 

사실 @Around 안에서 모든 것들을 다 해결할 수 있기 때문에 거의 대부분 @Around만 사용해도 모든 기능을 구현할 수 있다.

 

사진 : 인프런 스프링 강의

동일한 @Aspect 안에 1번부터 5번까지 실행된다면 위와 같은 실행 순서를 가지게 된다.

어드바이스가 적용되는 순서는 위와 같지만 호출 순서와 리턴 순서는 반대이다.

 

 

 

@Around 외에 다른 어드바이스들이 존재하는 이유

@Around("hello.aop.order.aop.Pointcuts.orderAndService()")
public void doBefore(ProceedingJoinPoint joinPoint) {
    log.info("[before] {}", joinPoint.getSignature());
}

@Around에서는 proceed를 실수로라도 호출하지 않으면 다음 대상이 호출되지 않는다. 하지만 @Before은 proceed를 호출하지 않아도 자동으로 다음 대상이 호출된다. 실수라 하더라도 매우 치명적인 실수가 된다.

 

또한 @Before, @After와 같은 어드바이스는 기능이 적긴 하지만 코드의 의도를 명확하게 파악할 수 있다는 장점이 있다.

 

 

 

그럼에도 불구하고 proceed를 잘 호출해서 @Around만 사용하는 것이 좋지 않을까 하는 개인적인 생각이다......