노션으로 다시 돌아갔습니다 😅

[AOP] Advice

by mignon25

어드바이스(Advice) 란?

  • 부가 기능
  • 조인 포인트에서 수행되는 코드
  • Aspect 를 언제 핵심 코드에 적용할지 정의

 

 

Advice 순서

  • Advice 는 기본적으로 순서를 보장하지 않는다.
  • 하나의 Aspect에 여러 어드바이스가 존재하면 순서를 보장받을 수 없다.
  • 순서를 지정하고 싶다면?
    • @Aspect 적용 단위로,
    • 별도의 클래스로 분리하고,
    • org.springframework.core.annotation.@Order 애너테이션을 적용
package section2.aop.order.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;

// 어드바이스에 순서 적용하기
@Slf4j
@Aspect
public class Aspect5 {

    @Aspect
    @Order(1) // 파라미터 값으로 순서 적용
    public static class LogAspect { // 순서를 적용할 @Aspect 단위로 별도의 클래스 생성
    
        @Around("section2.aop.order.aop.Pointcuts.allOrder()")
        public Object doLog(ProceedingJoinPoint joinPoint) {
            // Advice doLog() 로직
        }
        
    }

    @Aspect
    @Order(2) // 파라미터 값으로 순서 적용
    public static class TxAspect { // 순서를 적용할 @Aspect 단위로 별도의 클래스 생성
    
        @Around("section2.aop.order.aop.Pointcuts.orderAndService()")
        public Object doTransaction(ProceedingJoinPoint joinPoint) {
			// Advice doTransaction() 로직
        }
        
    }
}

다시 말해,

  1. 순서를 적용할 메서드를 별도의  클래스로 분리하고, 그 클래스에
  2. @Aspect 를 적용하고
  3. @Order() 를 적용하고 파라미터 값으로 순서를 넣어준다. 
  4. 그리고 각각의 메서드에 Advice 관련 애너테이션 추가 

 

 

Advice 종류

  • Before
  • After returning
  • After throwing
  • After(finally)
  • Around

 

 

@Before

  • 조인 포인트 실행 이전에 실행
  • 타겟 메서드가 실행되기 전에 처리해야할 필요가 있는 부가 기능을 메서드 호출 전에 실행
  • @Before Advice 를 구현한 메서드는 일반적으로 리턴타입이 void 이다.
    • @Before 를 사용하는 것은 리턴 값에는 별 관심이 없기 때문.
    • 리턴 값을 갖더라도 실제 Advice 적용 과정에 아무 영향이 없다.
    • @Before Advice 로는 타겟을 호출하는 방식을 제어할 수 없다. 
    • @Before Advice 를 적용해도 자동으로 다음 타겟이 정상적으로 호출된다.
  • JoinPoint 타입의 파라미터를 사용할 수 있다. 
    • JoinPoint : @Around 메서드의 파라미터로 사용되는 ProceedingJoinPoint 의 슈퍼 인터페이스
    • JoinPoint 타입은 메서드 실행지점(조인 포인트)에 대한 정보를 가져올 수는 있지만, 타겟 메서드를 실행하는 proceed() 메서드는 없다.
      => @Before Advice 메서드 종료 시 자동으로 다음 타겟 호출됨
  • 주의점 : 예외를 발생시킬 경우, 대상 객체의 메서드를 호출하지 않는다.
@Before("hello.xyz.aop.Pointcuts.orderAndService()")
public void doBefore(JoinPoint joinPoint) {
	// 조인 포인트 실행 전에 실행할 부가기능 로직
}

파라미터에는 포인트컷 표현식 또는 위의 예제처럼 포인트컷이 적용된 경로 + 메서드 이름를 적어도 된다.

(사용할 포인트 컷들을 모아놓고 사용할 수 있다.)

 

 

@AfterReturning

  • 타겟 오브젝트의 메서드가 실행(예외 없이 정상 종료된 경우)을 마친 뒤에 실행
  • 타겟이 정상 종료된 후에 호출되므로 타겟 메서드의 리턴 값을 참조할 수 있다.
  • 리턴 값을 참조할 때는 returning 속성 이용
  • value(또는 pointcut) : 포인트컷 표현식
  • returning
    • 리턴 값 자체를 변경할 수는 없으나, 리천 값이 레퍼런스 타입이라면 참조 오브젝트를 조작하는 것은 가능
    • 리턴 값을 전달받을 파라미터의 타입을 구체적으로 지정해주면, 리턴 값의 타입이 일치하는 경우에만 @AfterReturning가 실행된다. 
    • returning 속성에 사용되는 이름은 메서드의 파라미터 이름과 일치해야 한다. 
    • JoinPoint 와 함께 사용할 수도 있고, return 값만 파라미터로 사용할 수도 있다. 
    • JoinPoint 와 함께 사용한다면 JoinPoint 순서가 return값 파라미터보다 앞에 와야 한다. 
@AfterReturning(value = "hello.xyz.aop.myPointcut()", returning = "result")
public void doReturn(JoinPoint joinPoint, Object result) {
	// 타겟 메서드 정상 종료 후 실행할 부가기능 로직
}

근데 리턴 값을 전달받을 파라미터의 타입을 구체적으로 지정해준다는 것이 무슨 말이지...?

 

 

@AfterThrowing

  • 타겟 메서드가 호출되었을 때 예외가 발생하면 실행
  • throwing 속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 일치해야 한다.
  • throwing 으로 지정한 파라미터의 타입이 발생한 예외와 일치할 경우에만 어드바이스가 호출된다.
  • 모든 예외를 다 전달받으려면 Throwable 로 파라미터 타입을 지정 (?)
@AfterThrowing(value = "hello.xyz.Pointcuts.orderAndService()", throwing = "ex")
public void doThrowing(Joinpoint joinPoint, Exception ex) {
	// 타겟 메서드가 예외를 발생시켰을 때 수행할 부가기능 로직
}

 

 

@After

  • 메서드 실행이 정상 종료되었을 때와 예외가 발생했을 때 모두 실행
  • 코드에서 finally 를 사용했을 때와 비슷한 용도
  • 반드시 반환되어야 하는 리소스가 있거나, 메서드 실행 결과를 항상 로그로 남겨야 하는 경우 등에 사용
  • 리턴 값이나 예외를 직접 전달받을 수는 없다.

 

 

@Around

  • 프록시를 통해서 타겟 오브젝트의 메서드가 호출되는 전 과정을 모두 담을 수 있는 어드바이스
  • 파라미터로 ProceedingJoinPoint 를 사용
  • ProceedingJoinPoint 오브젝트의 proceed() 메서드를 통해 타겟 메서드 실행 및 결과 값도 받을 수 있다. 
  • 가장 강력한 기능을 가진 어드바이스
    • 조인 포인트 실행 여부 선택 : joinPoint.proceed()
    • 타겟 메서드(조인 포인트) 여러 번 실행
    • 전달 값 변환 : joinPoint.proceed(args[])
    • 예외 처리 : try, catch, finally 
    • 리턴 값 변환
@Around("myPointcut()")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
	
    // 타겟 메서드 실행 이전 이전에 부가기능 수행
    
    // 핵심 기능 수행
    Object output = joinPoint.proceed();
    
    // 타겟 메서드 실행 후 부가기능 수행
    
   	return output;
}

 

 

적절한 용도에 맞게 Advice를 사용하자.

모든 Advice는 @Around 만으로 전부 처리 가능하다. 

그러나 단지 호출 시점에 파라미터 값만 조사해서 로그로 남긴다거나 결과 값만 확인하는 것이 전부라면, ProceedingJoinPoint 파라미터를 선언하고, proceed()를 호출하고, 다시 결과를 리턴하는 것은 별 의미없는 반복 코드가 될 수 있다.

타겟 메서드 전후로 동시에 관여해야 하는 부가기능 로직이 아니라면 적절히 용도에 맞게 다른 어드바이스를 사용하자.

@Before, @After 와 같은 어드바이스의 경우 기능은 적지만, 대신 코드가 단순해지고, 기능이 적어진 만큼 어떤 일을 하는지 명확히 알 수 있게 된다. 

좋은 설계는 @Around 만 사용해서 모두 해결하는 것보다는 제약을 두어 역할을 명확하게 하여 실수를 미연에 방지하는 것이다. 

 

'Spring' 카테고리의 다른 글

Spring MVC 란?  (0) 2023.04.14
[빈 스코프] 빈 스코프와 프로토타입 스코프  (0) 2023.04.13
[AOP] JoinPoint  (0) 2023.04.12
[AOP] Pointcut  (0) 2023.04.12
조회한 빈이 모두 필요할 때 - List, Map 사용  (0) 2023.04.12

블로그의 정보

Mignon'S Dev Log

mignon25

활동하기