3. Declaring Advice, Introductions

2023. 5. 23. 17:04Spring Framework/Spring AOP

Declaring Advice

어드바이스는 포인트컷 표현식과 연관되어 있으며, 포인트컷에 의해 매칭된 메서드 실행 전, 후 또는 실행 중에 실행됩니다. 포인트컷 표현식은 인라인 포인트컷이거나 named pointcut을 참조할 수 있습니다.

Before Advice

@Before 애노테이션을 사용하여 어드바이스를 선언할 수 있습니다.

다음 예제는 인라인 포인트컷 표현식을 사용한 예제입니다:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

    @Before("execution(* com.xyz.dao.*.*(..))")
    public void doAccessCheck() {
        // ...
    }
}

이름이 지정된 포인트컷을 사용하는 경우, 이전 예제를 다음과 같이 다시 작성할 수 있습니다:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

    @Before("com.xyz.CommonPointcuts.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }
}

After Returning Advice

After returning 어드바이스는 매칭된 메서드 실행이 정상적으로 리턴될 때 실행됩니다. @AfterReturning 애노테이션을 사용하여 이를 선언할 수 있습니다.

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

    @AfterReturning("execution(* com.xyz.dao.*.*(..))")
    public void doAccessCheck() {
        // ...
    }
}

동일한 어드바이스 내에 여러 개의 어드바이스 선언(및 다른 멤버들)을 가질 수 있습니다.
여기서는 각각의 효과에 집중하기 위해 하나의 어드바이스 선언만을 보여줍니다.

때로는 어드바이스 본문에서 리턴된 실제 값에 접근해야 할 때가 있습니다. @AfterReturning의 리턴 값을 바인딩하는 형태를 사용하여 이에 접근할 수 있습니다. 다음 예제는 이를 보여줍니다:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

    @AfterReturning(
        pointcut="execution(* com.xyz.dao.*.*(..))",
        returning="retVal")
    public void doAccessCheck(Object retVal) {
        // ...
    }
}

returning 속성에서 사용된 이름[returning="retVal"]은 어드바이스 메서드의 파라미터 이름과 일치[예를 들어, 위 어드바이스 메서드 doAccessCheck의 첫번째 파라미터 이름(retVal)]해야 합니다. 메서드 실행이 리턴될 때, 이 메서드가 리턴 값은 해당 어드바이스 파라미터의 아규먼트 값으로 어드바이스 메서드에 전달됩니다. 또한, returning 절은 지정된 타입(Object)의 값을 리턴하는 메서드 실행만 매칭되도록 제한합니다.

After returning 어드바이스를 사용할 때 완전히 다른 참조를 리턴할 수 없음을 유의하십시오.

After Throwing Advice

Throwing 어드바이스는 매치된 메서드 실행이 예외를 던지며 종료될 때 실행됩니다. @AfterThrowing 애너테이션을 사용하여 선언할 수 있으며, 다음 예제를 참고하세요:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing("execution(* com.xyz.dao.*.*(..))")
    public void doRecoveryActions() {
        // ...
    }
}

종종 특정 타입의 예외가 발생할 때만 어드바이스가 실행되도록 하고, 예외 본문에 접근할 필요가 있습니다. throwing 속성을 사용하면 매칭을 제한할 수 있으며(필요한 경우, 그렇지 않으면 Throwable을 사용), 던져진 예외를 어드바이스 메서드의 파라미터로 바인딩할 수 있습니다. 다음 예제는 그 방법을 보여줍니다:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing(
        pointcut="execution(* com.xyz.dao.*.*(..))",
        throwing="ex")
    public void doRecoveryActions(DataAccessException ex) {
        // ...
    }
}

throwing 속성에 사용된 이름은 어드바이스 메서드의 파라미터 이름과 일치해야 합니다. 메서드 실행이 예외를 던지며 종료될 때, 해당 예외가 어드바이스 메서드의 해당 아규먼트로 전달됩니다. throwing 절은 지정된 타입의 예외를 던지는 메서드 실행으로만 매칭을 제한합니다(이 경우 DataAccessException).

@AfterThrowing 어드바이스는 일반적인 예외 처리 콜백을 나타내지 않는다는 점에 유의하세요. 구체적으로, @AfterThrowing 어드바이스 메서드는 조인 포인트(사용자 선언 타겟 메서드) 자체에서 발생한 예외만 수신해야 하며, 동반된 @After 또는 @AfterReturning 메서드에서 발생한 예외는 수신하지 않습니다.

After (Finally) Advice

After (finally) 어드바이스는 매치된 메서드 실행이 종료될 때 실행됩니다. @After 애너테이션을 사용하여 선언됩니다. After 어드바이스는 정상적인 리턴과 예외 리턴 조건 모두를 처리할 준비가 되어 있어야 합니다. 일반적으로 리소스를 해제하거나 유사한 목적으로 사용됩니다. 다음 예제는 after finally 어드바이스를 사용하는 방법을 보여줍니다:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;

@Aspect
public class AfterFinallyExample {

    @After("execution(* com.xyz.dao.*.*(..))")
    public void doReleaseLock() {
        // ...
    }
}

AspectJ에서 @After 어드바이스는 try-catch 문의 finally 블록과 유사한 "after finally 어드바이스"로 정의됩니다. 이는 조인 포인트(사용자가 선언한 타겟 메서드)에서 정상적으로 리턴되든 예외가 발생하든 모든 결과에 대해 호출됩니다. 이는 성공적인 정상 리턴에만 적용되는 @AfterReturning과는 대조적입니다.

Around Advice

Around 어드바이스는 마지막으로 소개할 어드바이스 종류로, 매치된 메서드 실행의 "주변(around)"에서 실행됩니다. 이 어드바이스는 메서드가 실행되기 전후에 작업을 수행하고, 메서드가 실제로 실행될지, 언제 실행될지, 그리고 어떻게 실행될지를 결정할 수 있는 기회를 제공합니다. Around 어드바이스는 주로 메서드 실행 전후에 상태를 thread-safe 방식으로 공유해야 할 때 사용됩니다. 예를 들어, 타이머를 시작하고 정지할 때 사용할 수 있습니다.

항상 요구 사항을 충족하는 가장 단순한 형태의 어드바이스를 사용해야 합니다.

예를 들어, before 어드바이스로 충분하다면 around 어드바이스를 사용하지 마십시오.

Around 어드바이스는 메서드 실행 전후에 추가 작업을 수행할 수 있게 해주는 AOP 기능입니다. 이를 구현하기 위해서는 다음과 같은 사항을 이해해야 합니다:

  1. @Around 애너테이션: 어라운드 어드바이스로 사용할 메서드에 @Around 애너테이션을 붙입니다. 이 애너테이션을 통해 해당 메서드가 어드바이스 메서드로 동작하게 됩니다.
  2. 리턴 타입: 어라운드 어드바이스 메서드는 항상 Object 타입을 리턴해야 합니다. 이는 타겟 메서드의 리턴 값을 처리하기 위함입니다.
  3. 첫 번째 파라미터: 어드바이스 메서드는 ProceedingJoinPoint 타입의 첫 번째 파라미터를 가져야 합니다. 이 파라미터는 실제로 호출될 타겟 메서드나 그 메서드의 정보를 나타냅니다.
  4. proceed() 메서드 호출: 어라운드 어드바이스 메서드 안에서 proceed() 메서드를 호출해야 타겟 메서드가 실행됩니다. 이 메서드를 호출하지 않으면 타겟 메서드가 실행되지 않습니다.
  5. 아규먼트 전달: proceed()를 호출할 때 아무런 인자도 전달하지 않으면, 원래 메서드에 전달된 아규먼트들이 그대로 사용됩니다.
    • 하지만, 만약 전달하는 아규먼트를 변경하고 싶다면, Object[] 배열을 아규먼트로 전달하는 proceed(Object[] args) 메서드를 사용할 수 있습니다. 이 배열 안에 있는 값들이 타겟 메서드의 새로운 아규먼트로 사용됩니다.

이렇게 어라운드 어드바이스를 사용하면 메서드 실행 전후에 로직을 추가하거나, 메서드의 아규먼트를 변경하여 실행할 수 있습니다.

 

"Spring AOP와 AspectJ의 proceed() 메서드 동작 차이와 호환성 고려사항"

1. Proceed 메서드의 기본 개념

  • proceed() 메서드는 어라운드 어드바이스에서 사용되는 메서드로, 타겟 메서드를 실행시킵니다.
  • 이 메서드를 호출하지 않으면 타겟 메서드가 실행되지 않습니다.

2. 두 가지 AOP 접근 방식: Spring AOP vs. AspectJ

  • Spring AOP: Spring 프레임워크에서 제공하는 AOP는 주로 프록시 기반으로 동작합니다. 실행 지점(Execution Join Points)에만 적용되며, 상대적으로 간단합니다.
  • AspectJ: AspectJ는 컴파일 타임 또는 로드 타임에 바이트코드를 조작하는 강력한 AOP 프레임워크입니다. 더 복잡한 AOP 기능을 제공합니다.

3. proceed() 메서드의 동작 차이점

  • Spring AOP에서의 proceed():
    • Spring AOP에서는 proceed(Object[] args)를 호출할 때, 이 args 배열은 타겟 메서드에 전달될 인자들을 나타냅니다.
    • proceed()를 호출할 때 인자를 제공하지 않으면 원래의 인자들이 사용됩니다. 만약 새로운 인자 배열을 제공하면, 타겟 메서드는 이 새로운 인자들을 사용하여 실행됩니다.
  • AspectJ에서의 proceed():
    • AspectJ에서 작성된 어라운드 어드바이스에서는 proceed() 메서드를 호출할 때 전달하는 인자들이 조금 다르게 처리됩니다.
    • 주요 차이점: proceed()에 전달되는 인자의 개수는 어드바이스가 받는 인자 수와 일치해야 합니다.
    • 예를 들어, 어드바이스 메서드가 두 개의 인자를 받는다면, proceed()도 두 개의 인자를 받아야 합니다. 이 인자들은 원래의 타겟 메서드에 전달될 인자와는 다를 수 있습니다.
    • 인자가 전달된 위치에 따라 타겟 메서드에 적용되는 값이 달라질 수 있습니다. 즉, 인자의 위치에 따라 원래 인자가 대체될 수 있습니다.

4. 호환성 문제

  • Spring AOP와 AspectJ는 proceed() 메서드를 처리하는 방식에 차이가 있기 때문에, 동일한 어드바이스 코드를 Spring AOP와 AspectJ에서 모두 사용하려면 이 차이를 고려해야 합니다.
  • @AspectJ 애너테이션을 사용하여 AspectJ 컴파일러와 위버를 사용하는 경우, Spring AOP의 단순한 인자 처리 방식과의 차이를 이해해야 합니다.

5. 해결책

  • Spring AOP와 AspectJ에서 모두 호환되는 어드바이스 코드를 작성하는 방법이 있습니다. 이러한 호환성에 대한 내용은 어드바이스 파라미터 섹션에서 다룹니다. 이 부분에서는 Spring과 AspectJ의 차이를 모두 고려한 어드바이스 파라미터 처리 방법이 설명됩니다.

Spring AOP와 AspectJ는 proceed() 메서드의 아규먼트 처리 방식에 차이가 있습니다. Spring AOP는 아규먼트 배열을 통해 타겟 메서드에 전달할 아규먼트들을 쉽게 변경할 수 있지만, AspectJ에서는 어드바이스 메서드의 아규먼트 개수와 proceed() 메서드에 전달되는 아규먼트 개수가 일치해야 합니다. 이 차이를 염두에 두고 코드를 작성해야 합니다.



Around 어드바이스에서 리턴된 값은 메서드 호출자가 보게 되는 리턴 값입니다. 예를 들어, 간단한 캐싱 어드바이스는 캐시에 값이 있으면 그 값을 리턴하고, 그렇지 않으면 proceed()를 호출하고 그 값을 리턴할 수 있습니다. proceed는 어드바이스 본문 내에서 한 번, 여러 번 또는 전혀 호출되지 않을 수 있습니다. 이들 모두는 합법적입니다.

Around 어드바이스 메서드의 리턴 타입을 void로 선언하면, null이 호출자에게 항상 리턴되며, proceed 호출의 결과가 무시됩니다. 따라서 Around 어드바이스 메서드는 Object를 리턴 타입으로 선언하는 것이 좋습니다. 어드바이스 메서드는 일반적으로 proceed() 호출에서 리턴된 값을 리턴해야 합니다. 기본 메서드가 void 리턴 타입이더라도 마찬가지입니다. 그러나 캐시된 값, 래핑된 값 또는 다른 값을 리턴할 수도 있습니다. 이는 사용 사례에 따라 다릅니다.

 

다음 예제는 around 어드바이스를 사용하는 방법을 보여줍니다:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AroundExample {

    @Around("execution(* com.xyz..service.*.*(..))")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // 스톱워치 시작
        Object retVal = pjp.proceed();
        // 스톱워치 정지
        return retVal;
    }
}

Advice Parameters

어떤 advice 메서드든 첫 번째 파라미터로 org.aspectj.lang.JoinPoint 타입의 파라미터를 선언할 수 있습니다. 특히 around advice는 ProceedingJoinPoint 타입의 첫 번째 파라미터를 선언해야 하는데, 이 타입은 JoinPoint의 하위 클래스입니다.

JoinPoint 인터페이스는 여러 유용한 메서드를 제공합니다:

  • getArgs(): 메서드 아규먼트를 리턴합니다.
  • getThis(): 프록시 객체를 리턴합니다.
  • getTarget(): 타겟 객체를 리턴합니다.
  • getSignature(): 현재 advice가 적용되고 있는 메서드의 설명을 반환합니다.
  • toString(): advice가 적용되는 메서드에 대한 유용한 설명을 출력합니다.

자세한 내용은 Javadoc을 참조하세요.

Passing Parameters to Advice

이전 섹션에서 리턴값이나 예외 값을 바인딩하는 방법(after returning 및 after throwing advice)을 봤습니다. 이와 비슷하게, 아규먼트 값을 advice 본문에 전달하기 위해 args의 바인딩 형식을 사용할 수 있습니다. args 표현식에서 타입 이름 대신 파라미터 이름을 사용하면 해당 파라미터에 전달된 값이 advice가 호출될 때 전달됩니다. 예를 들어 DAO 작업을 수행하는 메서드 중 첫 번째 파라미터로 Account 객체를 받는 메서드의 실행을 advice로 처리하고 싶다면, 다음과 같이 작성할 수 있습니다:

@Before("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
public void validateAccount(Account account) {
    // ...
}

여기서 args(account,..) 부분은 두 가지 역할을 합니다. 첫째, 아규먼트가 최소 하나 이상 존재하고, 그 아규먼트가 Account의 인스턴스인 경우에만 메서드 실행을 매칭합니다. 둘째, 실제 Account 객체를 account 파라미터를 통해 advice에서 사용할 수 있도록 합니다.

다른 방식으로는, Account 객체 값을 제공하는 포인트컷을 선언하고, 그 포인트컷을 advice에서 참조하는 방법이 있습니다. 예를 들어 다음과 같이 작성할 수 있습니다:

@Pointcut("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
private void accountDataAccessOperation(Account account) {}

@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
    // ...
}

프록시 객체(this), 타겟 객체(target), 애노테이션(@within, @target, @annotation, @args)도 유사하게 바인딩할 수 있습니다. 다음은 @Auditable 애노테이션이 적용된 메서드 실행을 매칭하고, 감사 코드를 추출하는 예제입니다:

다음은 @Auditable 애노테이션 정의를 보여줍니다:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
    AuditCode value();
}

다음은 @Auditable 메서드의 실행을 매칭하는 advice입니다:

@Before("com.xyz.Pointcuts.publicMethod() && @annotation(auditable)") 
public void audit(Auditable auditable) {
    AuditCode code = auditable.value();
    // ...
}

Advice Parameters and Generics

Spring AOP는 클래스 선언 및 메서드 파라미터에서 사용된 제네릭을 처리할 수 있습니다. 다음과 같은 제네릭 타입을 가지고 있다고 가정해 보겠습니다:

public interface Sample<T> {
    void sampleGenericMethod(T param);
    void sampleGenericCollectionMethod(Collection<T> param);
}

특정 파라미터 타입에 대해 메서드 타입의 인터셉션을 제한하려면, advice 파라미터를 인터셉트하려는 메서드의 파라미터 타입에 연결할 수 있습니다:

@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
    // Advice 구현
}

com.intheeast.aspectj.declaringadvice.service.Sample+는 Sample 인터페이스뿐만 아니라 Sample 인터페이스를 구현하는 모든 클래스에도 적용된다는 의미입니다.(SampleService 클래스의 메서드에도 적용됩니다)
타입 패턴에서 +는 해당 타입과 그 서브타입을 모두 포함시킨다는 의미

그러나 제네릭 컬렉션에 대해서는 이 접근 방식이 작동하지 않습니다. 다음과 같은 포인트컷은 정의할 수 없습니다:

@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<MyType> param) {
    // Advice 구현
}

이와 유사한 작업을 하려면, 파라미터 타입을 Collection<?>으로 지정하고 엘리먼트의 타입을 수동으로 확인해야 합니다.

Determining Argument Names

advice 호출 시 파라미터 바인딩은 포인트컷 표현식에서 사용된 이름과 advice 및 포인트컷 메서드 서명에 선언된 파라미터 이름을 매칭하는 방식으로 이루어집니다.

이 섹션에서는 AspectJ API가 파라미터 이름을 아규먼트 이름이라고 언급하기 때문에 아규먼트와 파라미터라는 용어를 혼용하여 사용합니다.

Spring AOP는 파라미터 이름을 결정하기 위해 다음과 같은 ParameterNameDiscoverer 구현체를 사용합니다. 각 구현체는 파라미터 이름을 발견할 기회를 제공받으며, 가장 먼저 성공한 구현체가 사용됩니다. 등록된 탐색기 중 파라미터 이름을 결정할 수 있는 구현체가 없으면 예외가 발생합니다.

  • AspectJAnnotationParameterNameDiscoverer: 해당 advice 또는 포인트컷 애노테이션의 argNames 속성을 통해 사용자가 명시적으로 지정한 파라미터 이름을 사용합니다. 자세한 내용은 명시적 아규먼트 이름(Explicit Argument Names)을 참조하세요.
  • KotlinReflectionParameterNameDiscoverer: Kotlin 리플렉션 API를 사용하여 파라미터 이름을 결정합니다. 이러한 API가 클래스패스에 있는 경우에만 사용됩니다.
  • StandardReflectionParameterNameDiscoverer: 표준 java.lang.reflect.Parameter API를 사용하여 파라미터 이름을 결정합니다. javac-parameters 플래그를 사용해 컴파일된 코드가 필요합니다(Java 8+ 권장).
  • AspectJAdviceParameterNameDiscoverer: 포인트컷 표현식, 반환, 및 throw 절에서 파라미터 이름을 유추합니다. 알고리즘에 대한 자세한 내용은 Javadoc을 참조하세요.

Explicit Argument Names

@AspectJ advice 및 포인트컷 애노테이션에는 argNames 속성이 있습니다. 이를 사용하여 애노테이션된 메서드의 아규먼트 이름을 지정할 수 있습니다.

@AspectJ aspect가 AspectJ 컴파일러(ajc)로 컴파일된 경우, 디버그 정보 없이도 필요한 정보가 유지되므로 argNames 속성을 추가할 필요가 없습니다.

마찬가지로, @AspectJ aspect가 javac을 사용하여 -parameters 플래그로 컴파일된 경우에도 argNames 속성을 추가할 필요가 없습니다.

다음은 argNames 속성을 사용하는 방법의 예제입니다:

@Before(
    value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", 
    argNames = "bean,auditable") 
public void audit(Object bean, Auditable auditable) {
    AuditCode code = auditable.value();
    // ... use code and bean
}

첫 번째 파라미터가 JoinPoint, ProceedingJoinPoint, 또는 JoinPoint.StaticPart 타입인 경우, argNames 속성에서 해당 파라미터 이름을 생략할 수 있습니다. 예를 들어, 앞서 언급한 advice를 변경하여 join point 객체를 수신하도록 한다면, argNames 속성에서 이를 포함할 필요가 없습니다:

@Before(
    value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", 
    argNames = "bean,auditable") 
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
    AuditCode code = auditable.value();
    // ... use code, bean, and jp
}

JoinPoint, ProceedingJoinPoint, 또는 JoinPoint.StaticPart 타입의 첫 번째 파라미터에 대한 특별 처리는 조인 포인트 컨텍스트를 수집하지 않는 advice 메서드에 특히 편리합니다. 이러한 상황에서는 argNames 속성을 생략할 수 있습니다. 예를 들어, 다음 advice는 argNames 속성을 선언할 필요가 없습니다:

@Before("com.xyz.Pointcuts.publicMethod()") 
public void audit(JoinPoint jp) {
    // ... use jp
}

Proceeding with Arguments

Spring AOP와 AspectJ에서 일관되게 작동하는 proceed 호출을 작성하는 방법에 대해 설명하겠습니다. 해결책은 메서드 파라미터 각각을 순서대로 advice 시그니처에 바인딩하는 것입니다. 다음은 그 방법의 예입니다:

@Around("execution(List<Account> find*(..)) && " +
        "com.xyz.CommonPointcuts.inDataAccessLayer() && " +
        "args(accountHolderNamePattern)") 
public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
        String accountHolderNamePattern) throws Throwable {
    String newPattern = preProcess(accountHolderNamePattern);
    return pjp.proceed(new Object[] {newPattern});
}

이 경우 대부분의 경우처럼 이러한 바인딩이 필요합니다.

Advice Ordering

여러 개의 어드바이스가 동일한 조인 포인트에서 실행되기를 원할 때는 어떻게 될까요? Spring AOP는 어드바이스 실행 순서를 결정하기 위해 AspectJ와 동일한 우선순위 규칙을 따릅니다. 우선순위가 가장 높은 어드바이스는 "진입 시"에 먼저 실행됩니다. 따라서 두 개의 before advice가 있다면, 우선순위가 가장 높은 어드바이스가 먼저 실행됩니다. 조인 포인트에서 "종료 시"에는 우선순위가 가장 높은 어드바이스가 마지막으로 실행됩니다. 따라서 두 개의 after 어드바이스가 있다면, 우선순위가 가장 높은 어드바이스가 두 번째로 실행됩니다.

서로 다른 애스펙트에 정의된 두 어드바이스가 동일한 조인 포인트에서 모두 실행되어야 할 경우, 별도로 지정하지 않으면 실행 순서는 정의되지 않습니다. 그러나 우선순위를 지정하여 실행 순서를 제어할 수 있습니다. 이는 일반적인 Spring 방식으로, 애스펙트 클래스에서 org.springframework.core.Ordered 인터페이스를 구현하거나, 애스펙트에 @Order 애노테이션을 추가하여 가능합니다. 두 애스펙트가 있을 때, Ordered.getOrder() 메서드에서 더 낮은 값을 반환하는 애스펙트(또는 애노테이션 값이 더 낮은 애스펙트)가 더 높은 우선순위를 가지게 됩니다.

특정 애스펙트의 각기 다른 어드바이스 유형들은 개념적으로 조인 포인트에 직접 적용되도록 설계되었습니다. 따라서, @AfterThrowing 어드바이스 메서드는 함께 동작하는 @After나 @AfterReturning 메서드로부터 예외를 전달받지 않도록 해야 합니다.
Spring Framework 5.2.7부터 동일한 @Aspect 클래스 내에서 동일한 조인 포인트에서 실행되어야 하는 어드바이스 메서드들은 다음과 같은 순서로 어드바이스 유형에 따라 우선순위가 지정됩니다. 가장 높은 우선순위에서 가장 낮은 우선순위로 나열하면: @Around, @Before, @After, @AfterReturning, @AfterThrowing입니다. 하지만, @After 어드바이스 메서드는 동일한 애스펙트 내에서 @AfterReturning이나 @AfterThrowing 어드바이스 메서드 이후에 효과적으로 호출되며, 이는 AspectJ의 "after finally advice" 개념을 따른 것입니다.
동일한 @Aspect 클래스에 정의된 동일한 유형의 두 개의 어드바이스(예를 들어, 두 개의 @After 어드바이스 메서드)가 동일한 조인 포인트에서 실행되어야 하는 경우, 그 순서는 정의되지 않습니다(이는 javac로 컴파일된 클래스에서 리플렉션을 통해 소스 코드 선언 순서를 가져올 방법이 없기 때문입니다). 이러한 경우, 각 @Aspect 클래스 내에서 조인 포인트마다 하나의 어드바이스 메서드로 통합하거나, 각 어드바이스를 별도의 @Aspect 클래스로 리팩터링하여 Ordered나 @Order를 통해 애스펙트 레벨에서 순서를 지정하는 것이 좋습니다.

Introductions

Introductions(AspectJ에서는 inter-type declarations라고도 함)는 특정 aspect가 advised된 객체가 주어진 인터페이스를 구현하도록 선언하고, 그 객체들을 대신하여 해당 인터페이스의 구현을 제공할 수 있게 합니다.

@DeclareParents 애노테이션을 사용하여 Introduction을 만들 수 있습니다. 이 애노테이션은 매칭되는 타입에 새로운 부모(즉, 새로운 인터페이스 구현)를 가지도록 선언하는 데 사용됩니다. 예를 들어, UsageTracked라는 인터페이스와 이 인터페이스를 구현한 DefaultUsageTracked라는 클래스가 있다고 가정해 보겠습니다. 다음의 aspect는 모든 서비스 인터페이스 구현체들이 UsageTracked 인터페이스를 구현하도록 선언합니다(예: JMX를 통한 통계 수집을 위해).

@Aspect
public class UsageTracking {

    @DeclareParents(value="com.xyz.service.*+", defaultImpl=DefaultUsageTracked.class)
    public static UsageTracked mixin;

    @Before("execution(* com.xyz..service.*.*(..)) && this(usageTracked)")
    public void recordUsage(UsageTracked usageTracked) {
        usageTracked.incrementUseCount();
    }

}

구현할 인터페이스는 애노테이션이 적용된 필드의 타입에 의해 결정됩니다. @DeclareParents 애노테이션의 value 속성은 AspectJ 타입 패턴을 사용합니다. 매칭되는 타입의 모든 빈은 UsageTracked 인터페이스를 구현하게 됩니다. 위 예제의 before advice에서 볼 수 있듯이, 서비스 빈은 UsageTracked 인터페이스의 구현체로 직접 사용할 수 있습니다. 빈을 프로그래밍 방식으로 접근할 때는 다음과 같이 작성할 수 있습니다:

UsageTracked usageTracked = context.getBean("myService", UsageTracked.class);