Spring AOP APIs 1[Pointcut API in Spring, Advice API in Spring]

2023. 5. 3. 16:04Spring Framework/Spring AOP

https://docs.spring.io/spring-framework/reference/core/aop-api.html

Pointcut API in Spring

이 섹션에서는 Spring이 중요한 포인트컷 개념을 어떻게 처리하는지 설명합니다.

Concepts

스프링의 포인트컷 모델은 advice 유형과 독립적으로 포인트컷을 재사용할 수 있게 해줍니다. 동일한 포인트컷으로 다양한 어드바이스를 타겟팅할 수 있습니다.

org.springframework.aop.Pointcut 인터페이스는 특정 클래스와 메서드를 타겟으로 어드바이스를 지정하기 위해 사용하는 핵심 인터페이스입니다. 전체 인터페이스는 다음과 같습니다:

public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();
}

Pointcut 인터페이스를 두 부분으로 나누면 클래스와 메서드 매칭 부분을 재사용하고, 세분화된 조합 작업(예: 다른 메서드 매처와 "합집합[union]" 수행)을 가능하게 합니다.

ClassFilter 인터페이스는 포인트컷을 특정 타겟 클래스 집합으로 제한하는 데 사용됩니다. matches() 메서드가 항상 true를 반환하면 모든 타겟 클래스가 일치합니다. 다음은 ClassFilter 인터페이스 정의입니다:

public interface ClassFilter {

    boolean matches(Class clazz);
}

MethodMatcher 인터페이스가 일반적으로 더 중요합니다. 전체 인터페이스는 다음과 같습니다:

public interface MethodMatcher {

    boolean matches(Method m, Class<?> targetClass);

    boolean isRuntime();

    boolean matches(Method m, Class<?> targetClass, Object... args);
}

matches(Method, Class) 메서드는 특정 클래스의 메서드가 포인트컷에 의해 일치하는지 여부를 테스트하는 데 사용됩니다. 이 평가를 AOP 프록시가 생성될 때 수행하여 모든 메서드 호출 시 테스트할 필요성을 없앨 수 있습니다. 만약 두 개의 아규먼트를 가지는 matches 메서드가 특정 메서드에 대해 true를 반환하고, MethodMatcherisRuntime() 메서드가 true를 반환하면, 세 개의 아규먼트를 가지는 matches 메서드는 각 메서드 호출 시마다 호출됩니다. 이를 통해 포인트컷은 타겟 어드바이스가 시작되기 직전에 메서드 호출에 전달된 아규먼트를 검사할 수 있습니다.

대부분의 MethodMatcher 구현은 정적이어서 isRuntime() 메서드가 false를 반환합니다. 이 경우 세개의 아규먼트를 가지는 matches 메서드는 호출되지 않습니다.

가능하다면 포인트컷을 정적으로 만들어 AOP 프레임워크가 AOP 프록시가 생성될 때 포인트컷 평가 결과를 캐시할 수 있게 하는 것이 좋습니다.


MethodMatcher 인터페이스와 AOP에서의 역할

MethodMatcher 인터페이스는 스프링 AOP에서 메서드 단위로 포인트컷(Pointcut)을 정의할 때 사용되는 중요한 인터페이스입니다. 이 인터페이스는 특정 메서드가 포인트컷의 조건에 맞는지 여부를 결정합니다.

public interface MethodMatcher {
    boolean matches(Method m, Class<?> targetClass);
    boolean isRuntime();
    boolean matches(Method m, Class<?> targetClass, Object... args);
}

메서드 설명

  1. matches(Method m, Class<?> targetClass):
    • 역할: 이 메서드는 지정된 메서드(m)와 해당 메서드가 속한 클래스(targetClass)가 포인트컷의 조건에 맞는지를 검사합니다.
    • 사용 시점: AOP 프록시가 생성될 때 한 번 실행됩니다. 이 메서드가 true를 반환하면, 해당 메서드에 대해 어드바이스가 적용됩니다.
    • 장점: 정적 매칭(static matching)을 수행하므로, 메서드 호출 시마다 이 매칭 로직을 반복할 필요가 없습니다. 따라서 성능에 유리합니다.
  2. isRuntime():
    • 역할: 이 메서드는 런타임 시점에서 추가적인 매칭 로직이 필요한지를 결정합니다.
    • 반환 값:
      • true를 반환하면, 메서드가 호출될 때마다 세 개의 아규먼트를 받는 matches(Method m, Class<?> targetClass, Object... args) 메서드가 호출됩니다.
      • false를 반환하면, 메서드 호출 시 추가적인 매칭이 수행되지 않으며, 캐시된 결과를 사용합니다.
    • 특징: 대부분의 구현에서는 정적 매칭이 더 효율적이므로 false를 반환합니다.
  3. matches(Method m, Class<?> targetClass, Object... args):
    • 역할: 이 메서드는 isRuntime()true일 때만 호출되며, 메서드의 실제 아규먼트(args)를 포함한 보다 세부적인 매칭을 수행합니다.
    • 사용 시점: 메서드 호출 시마다 실행되어, 특정 아규먼트 값에 따라 포인트컷 매칭 여부를 동적으로 결정할 수 있습니다.
    • 성능 고려 사항: 런타임 매칭은 매 호출 시마다 실행되므로, 성능에 영향을 미칠 수 있습니다. 가능한 경우 정적 매칭을 선호하는 것이 좋습니다.

정적 매칭과 런타임 매칭

1. 정적 매칭 (Static Matching)

  • 설명: 정적 매칭은 AOP 프록시가 생성될 때 수행되는 매칭입니다. 즉, 애플리케이션이 시작되거나 빈이 초기화될 때 특정 메서드가 포인트컷에 일치하는지 미리 결정됩니다.
  • 장점: 메서드 호출 시마다 매칭을 수행할 필요가 없기 때문에 성능이 더 좋습니다. matches(Method m, Class<?> targetClass) 메서드에서 매칭이 수행되고, 그 결과는 캐시됩니다.
  • 적용 사례: 메서드 이름이나 메서드가 속한 클래스와 같은 고정된 특성에 따라 어드바이스를 적용할 때 사용됩니다.

2. 런타임 매칭 (Runtime Matching)

  • 설명: 런타임 매칭은 메서드가 호출될 때마다 아규먼트 값과 같은 동적인 특성을 기반으로 매칭을 수행합니다. 이 경우, 매 호출 시마다 matches(Method m, Class<?> targetClass, Object... args) 메서드가 실행됩니다.
  • 단점: 매번 매칭이 수행되므로, 성능에 부정적인 영향을 미칠 수 있습니다.
  • 적용 사례: 특정 아규먼트 값에 따라 어드바이스 적용 여부를 동적으로 결정해야 할 때 사용됩니다. 예를 들어, 메서드 아규먼트가 특정 값일 때만 로깅을 수행하도록 하는 경우가 있습니다.

포인트컷 캐싱의 중요성

  • 캐싱의 이점: 포인트컷을 정적으로 만들어 AOP 프록시가 생성될 때 평가된 결과를 캐시하면, 메서드 호출 시 매칭을 다시 수행할 필요가 없어 성능이 개선됩니다. 캐싱된 결과를 사용하면 런타임 오버헤드를 줄일 수 있습니다.
  • 결론: 가능하다면 정적 매칭을 활용하여 AOP 프레임워크가 효율적으로 동작하도록 하는 것이 좋습니다.

요약

  • 정적 매칭: 성능에 유리하며, 대부분의 경우 사용됩니다.
  • 런타임 매칭: 특정 상황에서만 사용하며, 성능에 영향을 줄 수 있습니다.
  • isRuntime(): 런타임 매칭이 필요한지 여부를 결정합니다.
  • 캐싱: 정적 매칭 결과를 캐시하여 성능을 최적화할 수 있습니다.

이러한 설명을 바탕으로, 스프링 AOP에서 MethodMatcher가 메서드 매칭을 어떻게 관리하고, 성능을 최적화하기 위해 어떻게 설계되어 있는지 이해할 수 있습니다.


Operations on Pointcuts

스프링은 포인트컷에 대한 작업(특히 합집합 및 교집합)을 지원합니다.

합집합은 두 포인트컷 중 하나라도 일치하는 메서드를 의미하고, 교집합은 두 포인트컷 모두에 일치하는 메서드를 의미합니다. 합집합이 일반적으로 더 유용합니다. 포인트컷을 구성하려면 org.springframework.aop.support.Pointcuts 클래스의 정적 메서드를 사용하거나 동일한 패키지에 있는 ComposablePointcut 클래스를 사용할 수 있습니다. 그러나 AspectJ 포인트컷 표현식을 사용하는 것이 보통 더 간단합니다.

AspectJ Expression Pointcuts

2.0 버전 이후, 스프링에서 가장 중요한 포인트컷 유형은 org.springframework.aop.aspectj.AspectJExpressionPointcut입니다. 이는 AspectJ 포인트컷 표현식 문자열을 구문 분석하기 위해 AspectJ에서 제공하는 라이브러리를 사용하는 포인트컷입니다.

지원되는 AspectJ 포인트컷 프리미티브에 대한 논의는 이전 챕터를 참조하십시오.

Convenience Pointcut Implementations

스프링은 몇 가지 편리한 포인트컷 구현을 제공합니다. 이들 중 일부는 직접 사용할 수 있으며, 일부는 애플리케이션에 특화된 포인트컷에서 서브클래싱하기 위해 설계되었습니다.

Static Pointcuts

정적 포인트컷은 메서드와 타겟 클래스에 기반하며 메서드의 아규먼트를 고려할 수 없습니다. 대부분의 사용 사례에서 정적 포인트컷이 충분하며 최선의 선택입니다. 스프링은 메서드가 처음 호출될 때만 정적 포인트컷을 평가할 수 있습니다. 그 이후에는 각 메서드 호출 시마다 포인트컷을 다시 평가할 필요가 없습니다.

다음은 스프링에 포함된 몇 가지 정적 포인트컷 구현에 대한 설명입니다.

Regular Expression Pointcuts

정적 포인트컷을 지정하는 한 가지 명백한 방법은 정규 표현식을 사용하는 것입니다. 스프링 외의 여러 AOP 프레임워크도 이를 가능하게 합니다. org.springframework.aop.support.JdkRegexpMethodPointcut은 JDK의 정규 표현식 지원을 사용하는 일반 정규 표현식 포인트컷입니다.

JdkRegexpMethodPointcut 클래스를 사용하면 패턴 문자열 목록을 제공할 수 있습니다. 이 중 하나라도 일치하면 포인트컷이 true로 평가됩니다. (결과적으로 지정된 패턴의 합집합이 되는 포인트컷이 생성됩니다.)

다음은 JdkRegexpMethodPointcut을 사용하는 예제입니다:

<bean id="settersAndAbsquatulatePointcut"
        class="org.springframework.aop.support.JdkRegexpMethodPointcut">
    <property name="patterns">
        <list>
            <value>.*set.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>

스프링은 RegexpMethodPointcutAdvisor라는 편리한 클래스를 제공하여 어드바이스를 참조할 수 있게 해줍니다(조언은 인터셉터, 사전 조언, 예외 어드바이스 등일 수 있습니다). 내부적으로 스프링은 JdkRegexpMethodPointcut을 사용합니다. RegexpMethodPointcutAdvisor를 사용하면 하나의 빈이 포인트컷과 어드바이스를 모두 캡슐화하므로 연결이 간단해집니다. 다음은 이를 보여주는 예제입니다:

<bean id="settersAndAbsquatulateAdvisor"
        class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice">
        <ref bean="beanNameOfAopAllianceInterceptor"/>
    </property>
    <property name="patterns">
        <list>
            <value>.*set.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>

RegexpMethodPointcutAdvisor는 모든 어드바이스 유형과 함께 사용할 수 있습니다.

Attribute-driven Pointcuts

정적 포인트컷의 중요한 유형 중 하나는 메타데이터 기반 포인트컷입니다. 이는 메타데이터 속성(일반적으로 소스 수준 메타데이터)의 값을 사용합니다.

Dynamic pointcuts

동적 포인트컷은 정적 포인트컷보다 평가 비용이 더 많이 듭니다. 이들은 메서드 아규먼트뿐만 아니라 정적 정보를 고려합니다. 이는 메서드 호출 시마다 평가해야 하며, 아규먼트가 달라지므로 결과를 캐시할 수 없다는 것을 의미합니다.

주요 예는 control flow 포인트컷입니다.

Control Flow Pointcuts

스프링 제어 흐름 포인트컷은 개념적으로 AspectJ의 cflow 포인트컷과 유사하지만 덜 강력합니다. (현재는 다른 포인트컷에 의해 일치된 조인 포인트 아래에서 포인트컷을 실행하도록 지정할 방법이 없습니다.) 제어 흐름 포인트컷은 현재 호출 스택을 매칭합니다. 예를 들어, 조인 포인트가 com.mycompany.web 패키지의 메서드에 의해 호출되었거나 SomeCaller 클래스에 의해 호출된 경우 작동할 수 있습니다. 제어 흐름 포인트컷은 org.springframework.aop.support.ControlFlowPointcut 클래스를 사용하여 지정합니다.

제어 흐름 포인트컷은 다른 동적 포인트컷보다도 실행 시 평가 비용이 훨씬 더 비쌉니다. 자바 1.4에서는 그 비용이 다른 동적 포인트컷보다 약 5배 더 듭니다.

Pointcut Superclasses

스프링은 사용자 정의 포인트컷을 구현하는 데 유용한 포인트컷 슈퍼클래스를 제공합니다.

정적 포인트컷이 가장 유용하므로 StaticMethodMatcherPointcut을 서브클래싱하는 것이 좋습니다. 이 경우 하나의 추상 메서드만 구현하면 되며(다른 메서드를 재정의하여 동작을 사용자 정의할 수 있음), 다음은 StaticMethodMatcherPointcut을 서브클래싱하는 방법을 보여줍니다:

class TestStaticPointcut extends StaticMethodMatcherPointcut {

    public boolean matches(Method m, Class targetClass) {
        // 사용자 정의 기준에 맞는 경우 true 반환
    }
}

Advice API in Spring

Spring AOP에서 어드바이스(Advice)를 다루는 방법을 살펴보겠습니다.

Advice Lifecycles

각 어드바이스는 Spring 빈(bean)입니다. 어드바이스 인스턴스는 모든 타겟 객체에 공유될 수 있으며, 각 타겟 객체에 고유하게 존재할 수도 있습니다. 이는 클래스별 혹은 인스턴스별 어드바이스에 해당합니다.

클래스별 어드바이스는 일반적으로 사용됩니다. 이는 프록시 객체의 상태에 의존하지 않거나 새로운 상태를 추가하지 않는 트랜잭션 어드바이저와 같은 일반적인 어드바이스에 적합합니다. 이러한 어드바이스는 메서드와 아규먼트에 대해 작동합니다.

인스턴스별 어드바이스는 믹스인을 지원하기 위해 사용됩니다. 이 경우 어드바이스는 프록시 객체에 상태를 추가합니다.

동일한 AOP 프록시에서 공유 어드바이스와 인스턴스별 어드바이스를 혼합해서 사용할 수 있습니다.

Advice Types in Spring

Spring은 여러 가지 어드바이스 타입을 제공하며 임의의 어드바이스 타입을 지원하도록 확장할 수 있습니다. 이 섹션에서는 기본 개념과 표준 어드바이스 타입에 대해 설명합니다.

Interception Around Advice

Spring에서 가장 기본적인 어드바이스 타입은 Interception Around Advice입니다.

Spring은 메서드 인터셉션을 사용하는 around 어드바이스를 위한 AOP Alliance 인터페이스를 준수합니다. around 어드바이스를 구현하는 클래스는 다음 인터페이스를 구현해야 합니다:

public interface MethodInterceptor extends Interceptor {  
    Object invoke(MethodInvocation invocation) throws Throwable;  
}

invoke() 메서드에 전달되는 MethodInvocation 아규먼트는 호출된 메서드, 타겟 조인 포인트, AOP 프록시 및 메서드 아규먼트를 제공합니다. invoke() 메서드는 조인 포인트의 결과, 즉 리턴 값을 리턴해야 합니다.

다음은 간단한 MethodInterceptor 구현 예시입니다:

public class DebugInterceptor implements MethodInterceptor {

  public Object invoke(MethodInvocation invocation) throws Throwable {
      System.out.println("Before: invocation=[" + invocation + "]");
      Object rval = invocation.proceed();
      System.out.println("Invocation returned");
      return rval;
  }

}

MethodInvocationproceed() 메서드를 호출하는 것을 주목하세요. 이 메서드는 인터셉터 체인을 통해 조인 포인트로 진행됩니다. 대부분의 인터셉터는 이 메서드를 호출하고 리턴 값을 리턴합니다. 그러나 MethodInterceptor는 다른 값을 리턴하거나 proceed 메서드를 호출하는 대신 예외를 던질 수 있습니다. 하지만 특별한 이유가 없는 한, 이러한 동작은 하지 않는 것이 좋습니다.

MethodInterceptor 구현은 다른 AOP Alliance 준수 AOP 구현과의 상호운용성을 제공합니다. 이 섹션에서 다루는 다른 어드바이스 타입들은 Spring 고유의 방식으로 일반적인 AOP 개념을 구현합니다. 가장 특정한 어드바이스 타입을 사용하는 것이 이점이 있긴 하지만, 다른 AOP 프레임워크에서 이 애스펙트를 실행할 가능성이 있다면 MethodInterceptor around 어드바이스를 사용하는 것이 좋습니다. 포인트컷은 프레임워크 간에 상호운용되지 않으며, AOP Alliance는 현재 포인트컷 인터페이스를 정의하지 않습니다.

Before Advice

더 단순한 어드바이스 타입은 Before Advice입니다. 이 어드바이스는 메서드에 진입하기 전에 호출되므로 MethodInvocation 객체가 필요하지 않습니다.

Before Advice의 주요 장점은 proceed() 메서드를 호출할 필요가 없으며, 따라서 인터셉터 체인을 진행하지 못하게 될 가능성이 없다는 것입니다.

다음은 MethodBeforeAdvice 인터페이스입니다:

public interface MethodBeforeAdvice extends BeforeAdvice {  
    void before(Method m, Object\[\] args, Object target) throws Throwable;  
}

(Spring의 API 디자인은 필드 Before Advice도 허용하지만, 필드 인터셉션에 적용되는 일반적인 객체들이 있어 Spring이 이를 구현할 가능성은 낮습니다.)

리턴 타입이 void인 것을 주목하세요. Before Advice는 조인 포인트가 실행되기 전에 사용자 정의 동작을 삽입할 수 있지만, 리턴 값을 변경할 수는 없습니다. Before Advice가 예외를 던지면 인터셉터 체인의 추가 실행이 중단됩니다. 예외는 인터셉터 체인을 통해 전파됩니다. 예외가 체크되지 않거나 호출된 메서드의 시그니처에 포함된 경우, 클라이언트에 직접 전달됩니다. 그렇지 않으면 AOP 프록시에 의해 체크되지 않은 예외로 래핑됩니다.

다음은 Spring에서 메서드 호출 횟수를 세는 Before Advice의 예입니다:

public class CountingBeforeAdvice implements MethodBeforeAdvice {

  private int count;

  public void before(Method m, Object[] args, Object target) throws Throwable {
      ++count;
  }

  public int getCount() {
      return count;
  }

}

Before Advice는 모든 포인트컷과 함께 사용할 수 있습니다.

Throws Advice

Throws Advice는 조인 포인트가 예외를 던졌을 때 호출됩니다. Spring은 형식화된 Throws Advice를 제공합니다. org.springframework.aop.ThrowsAdvice 인터페이스에는 메서드가 없다는 점에 유의하세요. 이 인터페이스는 주어진 객체가 하나 이상의 형식화된 Throws Advice 메서드를 구현한다는 것을 식별하는 태그 인터페이스입니다. 이러한 메서드는 다음과 같은 형식이어야 합니다:

afterThrowing(\[Method, args, target\], subclassOfThrowable)

마지막 아규먼트만 필수입니다. 어드바이스 메서드는 메서드 및 아규먼트에 관심이 있는지 여부에 따라 하나 또는 네 개의 아규먼트를 가질 수 있습니다. 다음 목록은 Throws Advice의 예시 클래스입니다.

다음 어드바이스는 RemoteException이 발생했을 때(하위 클래스 포함) 호출됩니다:

public class RemoteThrowsAdvice implements ThrowsAdvice {

  public void afterThrowing(RemoteException ex) throws Throwable {
      // Do something with remote exception
  }

}

이전의 어드바이스와 달리, 다음 예시는 네 개의 아규먼트를 선언하여 호출된 메서드, 메서드 아규먼트 및 타겟 객체에 접근할 수 있습니다. 다음 어드바이스는 ServletException이 발생했을 때 호출됩니다:

public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

  public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
      // Do something with all arguments
  }


}

마지막 예시는 RemoteExceptionServletException 모두를 처리하는 메서드를 단일 클래스에 결합하여 사용하는 방법을 보여줍니다. 여러 개의 Throws Advice 메서드를 하나의 클래스에 결합할 수 있습니다. 다음 목록은 최종 예시를 보여줍니다:

public static class CombinedThrowsAdvice implements ThrowsAdvice {

  public void afterThrowing(RemoteException ex) throws Throwable {
      // Do something with remote exception
  }

  public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
      // Do something with all arguments
  }

}

Throws Advice 메서드가 예외를 던지면 원래의 예외를 덮어씁니다(즉, 사용자에게 전달되는 예외가 변경됩니다). 덮어쓰는 예외는 일반적으로 모든 메서드 시그니처와 호환되는 RuntimeException입니다. 그러나 Throws Advice 메서드가 체크된 예외를 던지는 경우, 타겟 메서드의 선언된 예외와 일치해야 하며, 따라서 특정 대상 메서드 시그니처에 어느 정도 결합됩니다. 타겟 메서드의 시그니처와 호환되지 않는 선언되지 않은 체크된 예외를 던지지 마십시오!

Throws Advice는 모든 포인트컷과 함께 사용할 수 있습니다.

After Returning Advice

Spring에서 After Returning Advice는 org.springframework.aop.AfterReturningAdvice 인터페이스를 구현해야 하며, 다음 목록은 이를 보여줍니다:

public interface AfterReturningAdvice extends Advice {

  void afterReturning(Object returnValue, Method m, Object[] args, Object target)
          throws Throwable;
}

After Returning Advice는 리턴 값(수정할 수 없음), 호출된 메서드, 메서드 아규먼트 및 타겟에 접근할 수 있습니다.

다음 After Returning Advice는 예외가 발생하지 않은 모든 성공적인 메서드 호출을 계산합니다:

public class CountingAfterReturningAdvice implements AfterReturningAdvice {

  private int count;

  public void afterReturning(Object returnValue, Method m, Object[] args, Object target)
          throws Throwable {
      ++count;
  }

  public int getCount() {
      return count;
  }

}

이 어드바이스는 실행 경로를 변경하지 않습니다. 예외를 던지면 리턴 값 대신 인터셉터 체인을 통해 전달됩니다.

After Returning Advice는 모든 포인트컷과 함께 사용할 수 있습니다.

Introduction Advice

Spring은 Introduction Advice를 Interception Advice의 특별한 유형으로 처리합니다.




AOP(Aspect-Oriented Programming)의 "Introduction Advice"는 특정 클래스나 객체에 새로운 메서드나 필드(속성)를 추가하기 위한 기능입니다. 이는 AOP의 주요 기능 중 하나로, 기존의 코드에 수정 없이 새로운 기능을 주입할 수 있게 해줍니다. 이를 통해 코드의 재사용성을 높이고, 중복을 줄이며, 특정 관심사를 모듈화하는 데 큰 도움이 됩니다.

Introduction Advice의 주요 개념

  1. 관심사의 분리(Separation of Concerns):
    • AOP의 핵심 목표 중 하나는 코드에서 서로 다른 관심사를 분리하는 것입니다. Introduction Advice는 이러한 관심사 중에서 특정 클래스에 새로운 기능을 추가하는 역할을 합니다. 예를 들어, 특정 객체가 특정 인터페이스를 구현하도록 만들거나, 새로운 속성을 추가할 수 있습니다.
  2. 타겟 클래스(Target Class):
    • Introduction Advice가 적용될 클래스나 인터페이스를 지정합니다. 이 클래스는 직접 수정되지 않으며, AOP 프레임워크가 실행 중에 이 클래스를 확장하여 새로운 메서드나 속성을 추가합니다.
  3. 인터페이스 구현(Implementing an Interface):
    • Introduction Advice는 주로 새로운 인터페이스를 구현하는 데 사용됩니다. 예를 들어, 클래스가 특정 인터페이스를 구현하지 않더라도, Introduction Advice를 통해 해당 인터페이스를 구현하도록 강제할 수 있습니다. 이를 통해, 런타임에 클래스에 새로운 기능을 동적으로 추가할 수 있습니다.
  4. AOP 프레임워크의 역할:
    • AOP 프레임워크(Spring AOP, AspectJ 등)는 Introduction Advice를 사용하여 기존 클래스에 새로운 인터페이스와 메서드를 추가하는 작업을 수행합니다. 이는 주로 프록시 패턴을 사용하여 구현되며, 클라이언트 코드에서는 변경된 내용을 인지하지 못한 채 수정된 기능을 사용할 수 있습니다.

예시

예를 들어, 특정 객체가 Auditable이라는 인터페이스를 구현하지 않았지만, 해당 객체에 감사(auditing) 기능을 추가하고 싶을 때, Introduction Advice를 사용해 해당 객체가 Auditable 인터페이스를 구현하도록 만들 수 있습니다. 이렇게 하면 해당 객체에 감사 관련 메서드가 추가되고, 이를 통해 감사 기능을 사용할 수 있습니다.

장점

  • 유연성: 코드의 변경 없이 런타임에 기능을 추가할 수 있습니다.
  • 모듈화: 특정 기능을 별도의 모듈로 분리하여 관리할 수 있습니다.
  • 재사용성: 여러 클래스에 동일한 기능을 쉽게 적용할 수 있습니다.

AOP의 Introduction Advice는 코드의 유연성과 모듈성을 높이는 데 매우 유용한 도구입니다. 이를 통해 기존 코드의 구조를 유지하면서도 새로운 기능을 동적으로 추가할 수 있어, 유지보수성과 확장성이 크게 향상됩니다.




Introduction은 IntroductionAdvisorIntroductionInterceptor를 필요로 하며, 다음 인터페이스를 구현해야 합니다:

public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);


}

 



Spring AOP의 맥락에서 Introduction은 기존 클래스를 수정하지 않고 새로운 메서드나 인터페이스를 추가할 수 있게 해주는 메커니즘입니다. 이는 특히 횡단 관심사(cross-cutting concerns)나 추가적인 동작을 동적으로 추가하는 데 유용합니다. Introduction을 구현하기 위해서는 IntroductionAdvisorIntroductionInterceptor라는 두 가지 주요 구성 요소가 필요합니다. 이들의 역할을 다음과 같이 설명할 수 있습니다:

1. IntroductionAdvisor

IntroductionAdvisor는 Introduction을 관리하고 적용하는 데 중요한 역할을 합니다. 주요 역할은 다음과 같습니다:

  • 도입할 인터페이스 정의: IntroductionAdvisor는 타겟 객체에 추가할 인터페이스를 지정합니다. 이는 getInterfaces() 메서드를 통해 이루어지며, 이 메서드는 도입될 인터페이스들을 반환합니다.
  • 타겟 클래스 필터링: IntroductionAdvisorgetClassFilter() 메서드를 통해 어떤 클래스에 Introduction을 적용할지 결정합니다. 이를 통해 특정 기준에 따라 선택적으로 Introduction을 적용할 수 있습니다.
  • 인터페이스 유효성 검사: validateInterfaces() 메서드는 IntroductionInterceptor가 도입할 인터페이스를 구현할 수 있는지를 확인합니다. 이 유효성 검사는 설정 오류를 방지하고 Introduction이 예상대로 작동할 수 있도록 도와줍니다.

2. IntroductionInterceptor

IntroductionInterceptor는 도입된 인터페이스의 실제 구현을 담당합니다. 이 인터셉터는 도입된 메서드 호출을 처리하는 역할을 합니다. 주요 역할은 다음과 같습니다:

  • 메서드 호출 가로채기: 도입된 인터페이스의 메서드가 호출되면, IntroductionInterceptor가 그 호출을 가로챕니다. 인터셉터의 invoke() 메서드는 이러한 메서드 호출을 처리합니다. 만약 메서드가 도입된 인터페이스에 속해 있다면, 인터셉터가 이 호출을 적절히 처리합니다.
  • 도입된 인터페이스 구현: IntroductionInterceptor 또는 그 delegate는 도입된 인터페이스의 메서드를 실제로 구현합니다. 도입된 인터페이스의 메서드가 호출될 때, 인터셉터가 이를 처리하고 필요한 로직을 수행합니다.
  • 원하지 않는 인터페이스 억제: suppressInterface(Class intf) 메서드를 사용하여 delegate가 구현했지만 AOP 프록시에 도입되지 말아야 할 인터페이스를 억제할 수 있습니다. 이는 노출할 인터페이스를 제어하는 데 유용합니다.

Introduction에서 이들의 역할 요약

  • IntroductionAdvisor: 도입할 인터페이스와 이를 적용할 대상을 지정하고, 도입이 올바르게 구성되었는지 확인하여 Introduction 과정을 관리합니다.
  • IntroductionInterceptor: 도입된 인터페이스의 구현을 제공하고, 그 인터페이스의 메서드 호출을 가로채어 처리합니다. 도입된 기능이 대상 객체에서 올바르게 실행되도록 보장합니다.

이 두 구성 요소는 함께 Spring AOP가 기존 코드베이스를 수정하지 않고도 객체의 기능을 동적으로 확장할 수 있게 해줍니다.




AOP Alliance의 MethodInterceptor 인터페이스에서 상속된 invoke() 메서드는 Introduction을 구현해야 합니다. 즉, 호출된 메서드가 도입된 인터페이스에 있는 경우, Introduction Interceptor는 메서드 호출을 처리해야 하며 proceed()를 호출할 수 없습니다.

Introduction Advice는 클래스 레벨에서만 적용되므로 메서드 레벨에서는 포인트컷과 함께 사용할 수 없습니다. Introduction Advisor와 함께 사용할 수 있습니다. 다음은 Introduction Advisor의 메서드입니다:

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {


  ClassFilter getClassFilter();

  void validateInterfaces() throws IllegalArgumentException;


}

public interface IntroductionInfo {


    Class<?>[] getInterfaces();


}

MethodMatcher가 없으므로 Introduction Advice와 관련된 포인트컷은 없습니다. 클래스 필터링만 논리적입니다.

getInterfaces() 메서드는 이 어드바이저에 의해 도입된 인터페이스를 리턴합니다.

validateInterfaces() 메서드는 설정된 IntroductionInterceptor가 도입된 인터페이스를 구현할 수 있는지 여부를 확인하기 위해 내부적으로 사용됩니다.

다음은 Spring 테스트 스위트의 예시로, 한 개 이상의 객체에 다음 인터페이스를 도입한다고 가정합니다:

public interface Lockable {  
  void lock();  
  void unlock();  
  boolean locked();  
}

이는 믹스인(mixin)을 나타냅니다. 어드바이스된 객체를 Lockable로 캐스팅하고, 객체의 타입에 관계없이 lockunlock 메서드를 호출할 수 있기를 원합니다. lock() 메서드를 호출하면 모든 setter 메서드가 LockedException을 던지도록 하고 싶습니다. 따라서 객체가 이를 전혀 인지하지 못한 상태에서 객체를 불변으로 만드는 기능을 추가할 수 있습니다. 이것은 AOP의 좋은 예입니다.

먼저, 무거운 작업을 수행할 IntroductionInterceptor가 필요합니다. 이 경우 org.springframework.aop.support.DelegatingIntroductionInterceptor 클래스를 확장합니다. IntroductionInterceptor를 직접 구현할 수 있지만, 대부분의 경우 DelegatingIntroductionInterceptor를 사용하는 것이 가장 좋습니다.

DelegatingIntroductionInterceptor는 도입을 실제 도입된 인터페이스의 구현에 위임하도록 설계되어 있으며, 이를 위해 인터셉션(interception)을 사용하는 것을 숨깁니다. 생성자 아규먼트를 사용하여 delegate를 임의의 객체로 설정할 수 있습니다. 기본 delegate(아규먼트가 없는 생성자를 사용할 경우)는 this입니다. 따라서 다음 예시에서 delegate는 DelegatingIntroductionInterceptorLockMixin 하위 클래스입니다. 기본적으로 delegate가 this-LockMixin 인스턴스-일 때, DelegatingIntroductionInterceptor 인스턴스는 delegate가 구현한 모든 인터페이스(IntroductionInterceptor 제외)를 찾아 이를 도입할 수 있습니다. LockMixin과 같은 하위 클래스는 suppressInterface(Class intf) 메서드를 호출하여 노출되지 말아야 할 인터페이스를 억제할 수 있습니다. 그러나 IntroductionInterceptor가 지원할 수 있는 인터페이스가 얼마나 많든지 간에, 사용된 IntroductionAdvisor가 실제로 노출되는 인터페이스를 제어합니다. 도입된 인터페이스는 대상이 동일한 인터페이스를 구현하고 있는 경우 이를 숨깁니다.

따라서 LockMixinDelegatingIntroductionInterceptor를 확장하고 Lockable을 자체적으로 구현합니다. 슈퍼 클래스는 자동으로 Lockable이 도입될 수 있음을 감지하므로 이를 명시할 필요가 없습니다. 이 방법을 통해 여러 인터페이스를 도입할 수 있습니다.

locked 인스턴스 변수를 사용하는 것을 주목하세요. 이는 대상 객체에 저장된 상태에 추가적인 상태를 효과적으로 추가합니다.

다음은 LockMixin 클래스의 예시입니다:

public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable {

  private boolean locked;

  public void lock() {
      this.locked = true;
  }

  public void unlock() {
      this.locked = false;
  }

  public boolean locked() {
      return this.locked;
  }

  public Object invoke(MethodInvocation invocation) throws Throwable {
      if (locked() && invocation.getMethod().getName().indexOf("set") == 0) {
          throw new LockedException();
      }
      return super.invoke(invocation);
  }

}

invoke() 메서드를 재정의할 필요가 없는 경우가 많습니다. DelegatingIntroductionInterceptor 구현은 메서드가 도입된 경우 delegate 메서드를 호출하고, 그렇지 않은 경우 조인 포인트로 진행되도록 호출합니다. 하지만 현재의 경우, setter 메서드는 잠금 모드에서 호출될 수 없도록 추가 검사를 해야 합니다.

필요한 introduction은 별개의 LockMixin 인스턴스를 보유하고, 도입된 인터페이스(이 경우 Lockable만)를 지정하기만 하면 됩니다. 더 복잡한 예는 introduction interceptor에 대한 참조를 가져올 수 있습니다(이는 프로토타입으로 정의됩니다). 이 경우 LockMixin에 대한 구성은 필요 없으므로 new를 사용하여 생성합니다. 다음은 LockMixinAdvisor 클래스의 예입니다:

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

  public LockMixinAdvisor() {
      super(new LockMixin(), Lockable.class);
  }

}

이 어드바이저는 설정이 필요하지 않으므로 매우 간단하게 적용할 수 있습니다. (그러나 IntroductionInterceptorIntroductionAdvisor 없이 사용할 수 없습니다.) 소개된 바와 같이, 어드바이저는 상태를 가지므로 인스턴스별로 관리해야 합니다. LockMixinAdvisor와 따라서 LockMixin의 다른 인스턴스가 각 어드바이스된 객체에 필요합니다. 어드바이저는 어드바이스된 객체 상태의 일부로 구성됩니다.

이 어드바이저는 Advised.addAdvisor() 메서드를 사용하여 프로그래밍 방식으로 적용할 수 있으며(권장되는 방법), XML 구성에서 다른 어드바이저처럼 적용할 수 있습니다. 아래에서 논의할 모든 프록시 생성 옵션, 즉 "자동 프록시 생성기"는 도입 및 상태가 있는 믹스인을 올바르게 처리합니다.