이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.
9. Spring AOP API
9.1 소개
이전 장에서 @AspectJ와 스키마 기반의 관점 정의를 사용하는 AOP에 대한 스프링 2.0과 그 이후 버전의 지원에 대해서 설명했다. 이번 장에서는 저수준 스프링 AOP API와 스프링 1.2 어플리케이션에서 사용하는 AOP 지원에 대해서 설명한다. 새로운 어플리케이션에서는 이전 장에서 설명한 스프링 2.0이상의 AOP지원을 사용하기를 권장하지만 이미 존재하는 어플리케이션에서 작업하거나 책이나 글을 읽는 경우에 스프링 1.2 방식의 예제를 봐야할 것이다. 스프링 3.0은 스프링 1.2와 하위호환성을 자기도 있으며 이번 장에서 설명하는 내용은 모두 스프링 3.0에서도 완전히 지원한다.
9.2 스프링의 포인트컷 API
스프링이 포인트컷의 핵심 개념을 어떻게 다루는 지 보자.
9.2.1 개념
스프링의 포인트컷 모델은 어드바이스 타입과 독립적으로 포인트컷을 재사용할 수 있다. 그래서 다른 대상 어드바이스에 같은 포인트컷을 사용할 수 있다.
org.springframework.aop.Pointcut 인터페이스가 특정 클래스와 메서드에 어드바이스를 적용하는데 사용하는 핵심 인터페이스이다. 전체 인터페이스는 다음에 나와있다.
1 2 3 4 5 6 7 8 | Java public interface Pointcut { ClassFilter getClassFilter(); MethodMatcher getMethodMatcher(); } |
Pointcut 인터페이스를 두 부분으로 분리해서 클래스와 메서드 매칭 부분과 세밀한 구성 작업(다른 메서드 매처(matcher)와의 "결합" 작업같은)을 재사용할 수 있다.
ClassFilter 인터페이스는 포인트컷을 주어진 대상 클래스의 세트로 제한하는데 사용한다. matches() 메서드가 항상 true를 반환한다면 모든 대상 클래스가 매치될 것이다.
1 2 3 4 5 6 | Java public interface ClassFilter { boolean matches(Class clazz); } |
일반적으로는 MethodMatcher 인터페이스가 더 중요하다. 전체 인터페이스는 다음에 나와있다.
1 2 3 4 5 6 7 8 9 10 | Java public interface MethodMatcher { boolean matches(Method m, Class targetClass); boolean isRuntime(); boolean matches(Method m, Class targetClass, Object[] args); } |
matches(Method, Class) 메서드는 대상 클래스의 주어진 메서드가 매치될 것인지를 검사하는데 사용한다. 이 평가작업은 메서드를 호출할 때마다 검사하는 것을 피하기 위해서 AOP 프록시를 생성할 때 수행할 수 있다. 2개의 아규먼트로 매칭하는 메서드가 주어진 메서드에 대해서 true를 반환하고 MethodMatcher에 대해서 isRuntime() 메서드가 true를 반환하면 3개의 아규먼트로 매칭하는 메서드는 메서드가 호출될 때마다 실행될 것이다. 이는 대상 어드바이스가 실행되기 이전에 포인트컷이 메서드 호출에 전달된 아규먼트를 바로 볼 수 있도록 한다.
대부분의 MethodMatcher는 static이다. 즉 MethodMatchers의 isRuntime() 메서드는 false를 반환한다. 이 경우에 3개의 아규먼트로 매칭하는 메서드는 실행되지 않을 것이다.
Tip
AOP 프레임워크가 AOP 프록시를 생성할 때 포인트컷의 평가 결과를 캐시할 수 있도록 가능한한 포인트컷을 스태틱으로 만들어라.
AOP 프레임워크가 AOP 프록시를 생성할 때 포인트컷의 평가 결과를 캐시할 수 있도록 가능한한 포인트컷을 스태틱으로 만들어라.
9.2.2 포인트컷 상에서의 연산작업
스프링은 포인트컷의 연산작업을 지원한다. 특히 union과 intersection.
- Union은 어떤 포인트컷이라도 매칭된 메서드를 의미한다.
- Intersection은 두 포인트컷이 모두 매칭된 메서드를 의미한다.
- 보통은 Union이 더 유용하다.
- org.springframework.aop.support.Pointcuts 클래스의 정적 메서드나 같은 팩키지의 ComposablePointcut 클래스를 사용해서 포인트컷을 구성할 수 있다. 하지만 일반적으로는 AspectJ 포인트컷 현식이 더 간단하다.
9.2.3 AspectJ 표현식 포인트컷
2.0부터 스프링이 사용하는 가장 중요한 포인트컷 타입은 org.springframework.aop.aspectj.AspectJExpressionPointcut이다. 이 포인트컷은 AspectJ 포인트컷 표현식 문자열을 파싱하기 위해 AspectJ가 제공하는 라이브러리를 사용한다.
지원하는 AspectJ 포인트컷 프리미티브를 설명한 이전 장을 참고해라.
9.2.4 편리한 포인트컷 구현체
스프링은 편리한 포인트컷 구현체를 다수 제공한다. 이 구현체 중 일부는 독창적으로 사용할 수 있다. 다른 구현체들은 어플리케이션에 한정적인 포인트컷의 하위 클래스가 되기 위해 만들어졌다.
9.2.4.1 정적(Static) 포인트컷
정적 포인트컷은 메서드와 대상 클래스에 기반을 두고 있고 메서드의 아규먼트를 참조할 수 없다. 대부분의 용도에는 정적 포인트컷으로도 충분하다(가장 좋다). 스프링에서 정적 포인트컷은 최초 메서드가 호출될 때 딱 한번만 평가할 수 있고 그 이후의 메서드 호출마다 포인트컷을 다시 평가할 필요는 없다.
스프링에 포함된 몇가지 정적 포인트컷 구현체를 살펴보자.
정규 표현식 포인트컷
정적 포인트컷을 지정하는 확실한 방법 중 하나는 정규표현식을 사용하는 것이다. 스프링을 포함한 다수의 AOP 프레임워크는 정규표현식을 사용하는 것이 가능하다. org.springframework.aop.support.JdkRegexpMethodPointcut이 JDK 1.4이상에서 지원하는 정규표현식을 사용하는 일반적인 정규표현식 포인트컷이다.
JdkRegexpMethodPointcut 클래스를 사용해서 패턴 문자열의 목록을 제공할 수 있다. 제공한 패턴 문자열 중에 일치하는 것이 있다면 포인트컷을 true라고 평가할 것이다. (그래서 효율적으로 이러한 포인트컷을 결합(union)한 결과가 된다.)
다음에서 사용방법을 보여준다.
1 2 3 4 5 6 7 8 9 10 11 | Xml <bean id="settersAndAbsquatulatePointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut"> <property name="patterns"> <list> <value>.*set.*</value> <value>.*absquatulate</value> </list> </property> </bean> |
스프링은 Advice(Advice는 인터셉터, before advice, throws advice등이 될 수 있다는 점을 기억해라.)도 참조할 수 있도록 RegexpMethodPointcutAdvisor라는 편리한 클래스를 제공한다. 스프링은 내부에서 JdkRegexpMethodPointcut를 사용할 것이다. 다음 예제처럼 RegexpMethodPointcutAdvisor을 사용해서 간단하게 연결(wiring)할 수 있다.(하나의 빈이 포인트컷과 어드바이스를 모두 은닉화한다.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Xml <bean id="settersAndAbsquatulateAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice"> <ref local="beanNameOfAopAllianceInterceptor"/> </property> <property name="patterns"> <list> <value>.*set.*</value> <value>.*absquatulate</value> </list> </property> </bean> |
RegexpMethodPointcutAdvisor는 어떤 타입의 Advice에도 사용할 수 있다.
속성 주도(Attribute-driven) 포인트컷
metadata-driven 포인트컷은 중요한 정적 포인트컷 타입이다. 이 포인트컷은 메타데이터 속성의 값을 사용한다. 일반적으로는 소스레벨의 메타데이터를 사용한다.
9.2.4.2 동적(Dynamic) 포인트컷
동적 포인트컷은 정적 포인트컷보다 평가하는데 비용이 더 많이 든다. 동적 포인트컷은 정적 정보와 마찬가지로 메서드의 arguments를 참조한다. 즉 동적 포인트컷은 메서드 호출시마다 매번 평가되어야 한다. 아규먼트가 달라질 것이므로 평가 결과는 캐싱할 수 없다. 주요한 예제는 흐름 제어(control flow) 포인트컷이다.
흐름 제어(Control flow) 포인트컷
스프링의 흐름제어 포인트컷은 개념적으로 AspectJ의 cflow 포인트컷과 비슷하지만 기능을 더 적다. (현재는 다른 포인트컷이 매칭한 조인 포인트하에서 포인트컷을 실행하도록 지정할 수 있는 방법이 없다.) 흐름 제어 포인트컷은 현재 호출스택(call stack)을 매칭한다. 예를 들어 com.mycompany.web 패키지의 메서드나 SomeCaller 클래스가 조인 포인트를 호출한 경우 발생(fire)할 것이다. 흐름 제어 포인트컷은 org.springframework.aop.support.ControlFlowPointcut 클래스를 사용해서 지정한다.
Note
흐름제어 포인트컷은 동적 포인트컷보다도 런타임에서 평가하는 비용이 약간 더 비싸다. 자바 1.4에서는 다른 동적 포인트컷보다 비용이 약 5배정도 크다.
흐름제어 포인트컷은 동적 포인트컷보다도 런타임에서 평가하는 비용이 약간 더 비싸다. 자바 1.4에서는 다른 동적 포인트컷보다 비용이 약 5배정도 크다.
9.2.5 포인트컷 슈퍼클래스
스프링은 자신만의 포인트컷을 구현할 수 있도록 유용한 포인트컷 슈퍼클래스들을 제공한다.
정적 포인트컷이 가장 유용하기 때문에 다음과 같이 StaticMethodMatcherPointcut의 하위클래스를 만들 것이다. 이 하위클래스는 딱 하나의 추상 메서드를 구현해야 한다.(동작을 바꾸기 위해 다른 메서드를 오버라이드하는 것도 가능하다.)
1 2 3 4 5 6 7 8 | Java class TestStaticPointcut extends StaticMethodMatcherPointcut { public boolean matches(Method m, Class targetClass) { // 커스텀 크리테리아가 일치하면 true를 반환한다 } } |
동적 포인트컷에 대한 슈퍼클래스들도 존재한다.
스프링 1.0 RC2 이상의 버전에서는 어떤 타입의 어드바이스로도 커스텀 포인트컷을 사용할 수 있다.
9.2.6 커스텀 포인트컷
스프링 AOP에서 포인트컷은 언어의 기능(AspectJ처럼)이 아니라 자바 클래스이기 때문에 정적이든 동적이든 커스텀 포인트컷을 선언할 수 있다. 스프링의 커스텀 포인트컷은 상황에 따라 복잡해 질 수 있다. 하지만 가능하다면 AspectJ 포인트컷 표현식 언어를 사용하기를 추천한다.
Note
스프링의 차기 버전에서는 JAC가 제안한 것과 같은 "시맨틱(semantic) 포인트컷"을 지원할 것이다. 예를 들면 "대상 클래스에서 인스턴스 변수를 변경하는 모든 메서드"같은 것이다.
스프링의 차기 버전에서는 JAC가 제안한 것과 같은 "시맨틱(semantic) 포인트컷"을 지원할 것이다. 예를 들면 "대상 클래스에서 인스턴스 변수를 변경하는 모든 메서드"같은 것이다.
9.3 스프링의 Advice API
이제 스프링 AOP가 어떻게 어드바이스를 다루는지 살펴보자.
9.3.1 어드바이스 생명주기
각각의 어드바이스는 스프링 빈이다. 어드바이스 인스턴스는 어드바이즈된 모든 객체사이에서 공유되거나 어드바이즈된 각 객체에 유일하게 공유될 수 있다. 각각 per-class 어드바이스와 per-instance 어드바이스다.
Per-class 어드바이스를 가장 자주 사용한다. Per-class 어드바이스는 트랜잭션 어드바이저같은 일반적인 어드바이스에 적절하다. 이 어드바이스는 프록시된 객체의 상태에 의존하거나 새로운 상태를 추가하지 말아야 한다. 이 어드바이스는 주로 메서드와 아규먼트에서 동작한다.
Per-instance 어드바이스는 믹스인을 사용하기 위한 인트로덕션에 적합하다. 이러한 경우 어드바이스가 프록시된 객체에 상태를 추가한다.
같은 AOP 프록시에서 공유된 per-instance 어드바이스를 섞어서 사용할 수 있다.
9.3.2 스프링의 어드바이스 타입
스프링은 여러 가지 독창적인 어드바이스 타입을 제공하고 임의의 어드바이스 타입을 지원하기 위해 확장할 수 있다. 표준 어드바이스 타입과 기본적인 개념을 살펴보자.
9.3.2.1 Interception around advice
interception around advice가 스프링에서 가장 기본적인 어드바이스 타입이다.
스프링은 메서드 가로채기(interception)를 사용하는 around advice에 AOP Alliance 인터페이스를 따른다. around advice를 구현하는 MethodInterceptor는 다음의 인터페이스를 구현해야 한다.
1 2 3 4 5 6 | Java public interface MethodInterceptor extends Interceptor { Object invoke(MethodInvocation invocation) throws Throwable; } |
invoke() 메서드의 MethodInvocation 아규먼트는 호출되는 메서드, 대상 조인포인트, AOP 프록시, 메서드의 아규먼트를 노출한다. invoke() 메서드는 반드시 호출결과인 조인포인트의 반환값을 반환해야 한다.
간단한 MethodInterceptor 구현체는 다음과 같다.
1 2 3 4 5 6 7 8 9 10 11 | Java 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; } } |
MethodInvocation의 proceed() 메서드를 호출한다. 이는 조인포인트쪽으로 인터셉터 체인을 따라 진행된다. 대부분의 인터셉터는 이 메서드를 호출하고 그 결과값을 반환할 것이다. 하지만 다른 around advice처럼 MethodInterceptor도 다른 값을 반환하거나 proceed 메서드를 호출하지 않고 예외를 던질 수 있다. 하지만 합당한 이유가 없이는 이렇게 되기를 바라지 않을 것이다!
Note
MethodInterceptor는 다른 AOP Alliance 호환 AOP 구현체와의 상호운용성(interoperability)을 제공한다. 이번 섹션의 남은 부분에서 얘기하는 다른 어드바이스 타입들은 일반적인 AOP 개념을 구현했지만 스프링식으로 구현했다. 가장 구체적인 어드바이스 타입을 사용할 때의 장점이 있지만 다른 AOP 프레임워크에서 관점을 실행하고 싶다면 MethodInterceptor around advice을 사용해라. 현재 포인트컷은 프레임워크간에 상호운용성이 없고 AOP Alliance는 포인트컷 인터페이스를 정의하고 있지 않다.
MethodInterceptor는 다른 AOP Alliance 호환 AOP 구현체와의 상호운용성(interoperability)을 제공한다. 이번 섹션의 남은 부분에서 얘기하는 다른 어드바이스 타입들은 일반적인 AOP 개념을 구현했지만 스프링식으로 구현했다. 가장 구체적인 어드바이스 타입을 사용할 때의 장점이 있지만 다른 AOP 프레임워크에서 관점을 실행하고 싶다면 MethodInterceptor around advice을 사용해라. 현재 포인트컷은 프레임워크간에 상호운용성이 없고 AOP Alliance는 포인트컷 인터페이스를 정의하고 있지 않다.
9.3.2.2 Before advice
더 간단한 어드바이스 타입은 before advice이다. 이 어드바이스는 메서드에 진입하기 전에만 호출될 것이므로 MethodInvocation 객체가 필요하지 않다.
before advice의 가장 큰 장점은 proceed() 메서드를 호출할 필요가 없으므로 인터셉터 체인을 따라 진행되는데 우연히라도 실패할 가능성이 없다는 것이다.
MethodBeforeAdvice 인터페이스는 다음에 나와있다. (일반적인 객체를 필드 인터셉션에 적용하고 스프링이 이를 구현할 것 같지도 않지만 스프링의 API 디자인은 어드바이스 이전의 필드를 허용할 것이다.)
1 2 3 4 5 6 | Java public interface MethodBeforeAdvice extends BeforeAdvice { void before(Method m, Object[] args, Object target) throws Throwable; } |
반환 타입은 void이다. before advice는 조인 포인트를 실행하기 전에 임의의 동작을 추가할 수 있지만 반환값을 바꿀 수는 없다. before advice가 예외를 던지면 인터셉터 체인의 이어진 실행을 중단할 것이다. 예외는 인터셉터 체인으로 전파된다. 예외가 언체크드(unchecked)이면(또는 호출된 메서드의 시그니쳐상에서) 클라이언트에 직접 예외가 전달된다. 예외가 언체크드가 아니라면 AOP 프록시가 언체크드 익셉션으로 감쌀 것이다.
다음은 모든 메서드 호출 횟수를 세는 스프링의 before advice의 예제다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Java 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; } } |
Tip
Before advice는 어떤 포인트컷과도 사용할 수 있다.
Before advice는 어떤 포인트컷과도 사용할 수 있다.
9.3.2.3 Throws advice
Throws advice는 조인포인트가 예외를 던진 경우 조인포인트가 반환된 후에 호출된다. 즉 org.springframework.aop.ThrowsAdvice 인터페이스는 어떤 메서드도 가지고 있지 않다. 이 인터페이스는 하나 이상의 throws advice 메서드를 구현한 주어진 객체를 식별하는 tag 인터페이스이다. 이는 다음 형태가 되어야 한다.
1 2 3 | Java afterThrowing([Method, args, target], subclassOfThrowable) |
마지막 아규먼트만 필수 아규먼트다. 메서드 시그니처는 어드바이스 메서드가 메서드와 아규먼트에 관계되었는 지에 따라 한개나 네개의 아규먼트를 가질 것이다. 다음 클래스들은 throws advice의 예제들이다.
다음의 어드바이스는 RemoteException가 던져질 때(서브클래스 포함) 호출된다.
1 2 3 4 5 6 7 8 | Java public class RemoteThrowsAdvice implements ThrowsAdvice { public void afterThrowing(RemoteException ex) throws Throwable { // 원격 예외로 무언가를 한다 } } |
ServletException가 던져졌을 때 다음의 어드바이스가 호출된다. 위의 어드바이스와는 달리 호출된 메서드, 메서드 아규먼트, 대상 객체에 접근하기 위해서 4개의 아규먼트를 선언한다.
1 2 3 4 5 6 7 8 | Java public class ServletThrowsAdviceWithArguments implements ThrowsAdvice { public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) { // Do something with all arguments } } |
마지막 예제는 하나의 클래스에서 RemoteException과 ServletException을 다루는 두 메서드를 어떻게 사용할 수 있는지를 설명한다. 하나의 클래스에서 throws advice 메서드를 몇개든 조합할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 | Java public static class CombinedThrowsAdvice implements ThrowsAdvice { public void afterThrowing(RemoteException ex) throws Throwable { // 원격 예외로 무언가를 한다 } public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) { // 모든 아규먼트로 무언가를 한다 } } |
Note: throws-advice 메서드 자체가 예외를 던지면 원래의 예외를 오버라이드할 것이다.(예시: 사용자에게 던져진 예외를 변경한다) 오버라이딩된 예외는 일반적으로 RuntimeException이 될 것이다. 이는 어떤 메서드 시그니처와도 호환성을 가진다. 하지만 throws-advice 메서드가 체크드 익셉션을 던지면 대상 메서드에서 선언된 예외와 일치해야 하므로 특성 대상 메서드 시그니처와 약간 커플링이 생긴다. 대상 메서드의 시그니처와 맞지 않는 선언되지 않은 체크드 익셉션을 던지지 마라!
Tip
Throws advice는 어떤 포인트컷과도 사용할 수 있다.
Throws advice는 어떤 포인트컷과도 사용할 수 있다.
9.3.2.4 After Returning advice
스프링에서 after returning advice는 다음과 같이 org.springframework.aop.AfterReturningAdvice 인터페이스를 반드시 구현해야 한다.
1 2 3 4 5 6 | Java public interface AfterReturningAdvice extends Advice { void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable; } |
after returning advice는 반환값(수정불가), 호출된 메서드, 메서드의 아규먼트, 대상객체에 접근한다.
다음의 after returning advice는 예외를 던지지 않고 성공적으로 호출된 모든 메서드의 수를 센다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Java 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; } } |
이 어드바이스는 실행 경로를 변경하지 않는다. 예외를 던질 경우에는 반환값을 반환하지 않고 예외를 인터셉터 체인에 던진다.
Tip
After returning advice는 어떤 포인트컷과도 사용할 수 있다.
After returning advice는 어떤 포인트컷과도 사용할 수 있다.
9.3.2.5 Introduction advice
스프링은 인트로덕션 어드바이스를 인터셉션(interception) 어드바이스의 특별한 종류처럼 다룬다.
인트로덕션은 다음의 인터페이스를 구현한IntroductionAdvisor와 IntroductionInterceptor를 필요로 한다.
1 2 3 4 5 6 | Java public interface IntroductionInterceptor extends MethodInterceptor { boolean implementsInterface(Class intf); } |
AOP Alliance MethodInterceptor 인터페이스에서 상속받은 invoke() 메서드는 인트로덕션을 구현해야 한다. 즉, 인트로덕션이 된 인터페이스(introduced interface)에서 메서드가 호출되면 인트로덕션 인터셉터가 메서드 호출을 다루는 책임을 담당한다. 이는 proceed()를 호출할 수 없다.
인트로덕션 어드바이스는 메서드가 아닌 클래스에만 적용되기 때문에 모든 포인트컷과 사용할 수 없다. 인트로덕션 어드바이스는 다음의 메서드를 가진 IntroductionAdvisor과 함께만 사용할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | Java public interface IntroductionAdvisor extends Advisor, IntroductionInfo { ClassFilter getClassFilter(); void validateInterfaces() throws IllegalArgumentException; } public interface IntroductionInfo { Class[] getInterfaces(); } |
인트로덕션 어드바이스와 연관된 MethodMatcher는 존재하지 않는다.(그러므로 Pointcut도 없다.) 클래스 필터링만이 적합하다.
getInterfaces() 메서드는 이 어드바이저가 인트로덕션한 인터페이스를 반환한다.
validateInterfaces() 설정된 IntroductionInterceptor로 인트로덕션이 된 인터페이스를 구현할 수 있는지를 확인하기 위해서 validateInterfaces() 메서드를 내부적으로 사용한다 .
스프링 테스트슈트에 있는 간단한 예제를 보자. 다음의 인터페이스를 하나이상의 객체에 인트로덕션하기를 원한다고 가정하자.
1 2 3 4 5 6 7 | Java public interface Lockable { void lock(); void unlock(); boolean locked(); } |
이 예제는 믹스인(mixin)을 설명해 준다. 어드바이즈된 객체의 타입이 무엇이든간에 Lockable로 캐스팅 할 수 있고 lock, unlock 메서드를 호출할 수 있기를 원한다. lock() 메서드를 호출하면 모든 setter 메서드가 LockedException를 던져야 한다. 그러므로 객체에 대한 정보가 없이도 객체를 불변(immutable)하게 만들 수 있는 관점을 추가할 수 있다. 이는 AOP의 좋은 예이다.
첫째로 힘든 일을 수행하는 IntroductionInterceptor가 필요하다. 이 경우에는 편리한 org.springframework.aop.support.DelegatingIntroductionInterceptor 클래스를 확장한다. 직접 IntroductionInterceptor를 구현할 수도 있지만 대부분의 경우에 DelegatingIntroductionInterceptor를 사용하는 것도 최선이다.
DelegatingIntroductionInterceptor는 인트로덕션은 인트로덕션된 인터페이스의 실제 구현체로 위임하도록 설계되었고 인터페이스는 이 작업을 하는 인터셉션의 사용을 감추고 있다. 위임은 생성자 아규먼트를 사용해서 어떤 객체에든 설정할 수 있다. 기본 위임은(아규먼트가 없는 생성자를 사용하는 경우) 다음과 같다. 그러므로 아래 예제에서 위임은 DelegatingIntroductionInterceptor의 하위클래스 LockMixin가 된다. 제공된 위임인(기본값이다) DelegatingIntroductionInterceptor 인스턴스는 위임(IntroductionInterceptor를 제외한)이 구현한 모든 인스턴스를 찾고 찾아낸 인스턴스에 인트로덕션을 지원할 것이다. 이는 노출되지 않아야 하는 차단된(suppress) 인터페이스에 suppressInterface(Class intf) 메서드를 호출하는 LockMixin같은 하위클래스에도 가능하다. 하지만 얼마나 많은 인터페이스가 지원하기 위해 IntroductionInterceptor를 준비하고 IntroductionAdvisor를 사용한 얼마나 많은 인터페이스가 실제로 노출할 인터페이스를 제어하는 지는 중요하지 않다. 인트로덕션이 된 인터페이스는 대상객체로 같은 인터페이스의 모든 구현체를 감출 것이다.
그러므로 LockMixin는 DelegatingIntroductionInterceptor의 하위클래스가 되고 Lockable 자체를 구현한다. 인트로덕션에 Lockable이 지원할 수 있는 슈퍼클래스를 자동적으로 선택하기 때문에 슈퍼클래스를 지정할 필요는 없다. 이 방법으로 인터페이스를 몇개든지 도입(introduce)할 수 있다.
locked 인스턴스 변수의 사용에 주목해라. 이는 대상객체가 가지는 부가적인 상태를 효과적으로 추가한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | Java 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 메서드를 호출하고 그렇지 않으면 조인포인트로 진행된다)로도 보통은 충분하다. 현시점에서는 locked 모드인 경우 호출될 수 있는 setter 메서드가 없는지 확인할 필요가 있다.
인트로덕션 어드바이저에 필요한 것은 간단하다. 개별적인 LockMixin 인스턴스를 가지고 인트로덕션이 되는 인터페이스를 지정하는 것이 해야하는 작업의 전부이다. 이 예제의 경우에는 Lockable 뿐이다. 더 복잡한 예제에서는 인트로덕션 인터셉터(프로토타입으로 정의되었을 것이다)에 대한 참조를 받을 것이다. 이 경우에 LockMixin에 절적한 설정이 없기 때문에 new로 생성한다.
1 2 3 4 5 6 7 8 | Java public class LockMixinAdvisor extends DefaultIntroductionAdvisor { public LockMixinAdvisor() { super(new LockMixin(), Lockable.class); } } |
이 어드바이저를 아주 간단하게 적용할 수 있다. 이 어드바이저는 설정이 필요없다. (하지만 어드바이저는 필수적이다. IntroductionAdvisor없이는 IntroductionInterceptor를 사용할 수 없다.) 어드바이저가 상태를 가지기(stateful) 때문에 인트로덕션과 사용할 때는 언제나 어드바이저는 per-instance가 되어야 한다. 어드바이즈된 각각의 객체마다 개별적인 LockMixinAdvisor 인스턴스(그리고 LockMixin)가 필요하다. 어드바이저는 어드바이즈된 객체의 상태의 일부분으로 구성된다.
다른 어드바이저처럼 Advised.addAdvisor() 메서드를 사용하거나 XML설정(추천하는 방법이다)을 사용해서 이 어드바이저를 프로그래밍적으로 적용할 수도 있다. "auto proxy creators"를 포함해서 아래에서 설명하는 모든 프록시 생성 선택권들(proxy creation choices은 인트로덕션과 상태를 가진(stateful) 믹스인을 제대로 다룬다.
9.4 스프링의 어드바이저(Advisor) API
스프링에서 어드바이저는 포인트컷 표현식과 연결된 하나의 어드바이스 객체를 가진 관점이다.
인트로덕션의 특수한 경우를 제외한 모든 어드바이저는 모든 어드바이스와 함게 사용할 수 있다. org.springframework.aop.support.DefaultPointcutAdvisor가 가장 일반적으로 사용하는 어드바이저 클래스다. 예를 들어 이 클래스를 MethodInterceptor, BeforeAdvice, ThrowsAdvice와 함께 사용할 수 있다.
스프링에서 어드바이저 타입과 어드바이스 타입을 같은 AOP프록시안에 섞을 수 있다. 예를 들어 하나의 프록시 설정안에서 interception around advice, throws advice, before advice를 사용할 수 있다. 스프링은 자동적으로 필수적인 인터셉터 체임을 생성할 것이다.
댓글 없음:
댓글 쓰기