Loading...
Spring Framework Reference Documentation 7.0.2의 Advice API in Spring의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
Now we can examine how Spring AOP handles advice.
Each advice is a Spring bean입니다. An advice instance는 모든 advised objects 전반에 걸쳐 공유되거나 각 advised object마다 고유할 수 있습니다. 이것은 per-class 또는 per-instance advice에 해당합니다.
Per-class advice는 가장 자주 사용됩니다. 이는 transaction advisors와 같은 일반적인 advice에 적합합니다. 이러한 것들은 proxied object의 상태에 의존하지 않거나 새로운 state를 추가하지 않습니다. 단지 method와 arguments에 대해 동작할 뿐입니다.
Per-instance advice는 introductions에 적합하며, mixins을 지원하기 위한 것입니다. 이 경우, advice는 proxied object에 state를 추가합니다.
동일한 AOP proxy에서 shared advice와 per-instance advice를 혼합해서 사용할 수 있습니다.
Spring은 여러 advice type을 제공하며, 임의의 advice type을 지원하도록 확장 가능합니다. 이 섹션에서는 기본 개념과 표준 advice type을 설명합니다.
Spring에서 가장 기본적인 advice type은 _interception around advice_입니다.
Spring은 method
interception을 사용하는 around advice에 대해 AOP Alliance interface를 준수합니다.
Around advice를 구현하는 classes는 따라서 org.aopalliance.intercept package의
다음 MethodInterceptor interface를 구현해야 합니다:
1public interface MethodInterceptor extends Interceptor { 2 3 Object invoke(MethodInvocation invocation) throws Throwable; 4}
invoke() method에 대한 MethodInvocation argument는 호출되는 method,
target join point, AOP proxy, 그리고 method에 대한 arguments를 노출합니다.
invoke() method는 invocation의 결과를 반환해야 합니다. 일반적으로 join point의
return value입니다.
다음 예제는 간단한 MethodInterceptor 구현을 보여 줍니다:
1public class DebugInterceptor implements MethodInterceptor { 2 3 public Object invoke(MethodInvocation invocation) throws Throwable { 4 System.out.println("Before: invocation=[" + invocation + "]"); 5 Object result = invocation.proceed(); 6 System.out.println("Invocation returned"); 7 return result; 8 } 9}
1class DebugInterceptor : MethodInterceptor { 2 3 override fun invoke(invocation: MethodInvocation): Any { 4 println("Before: invocation=[$invocation]") 5 val result = invocation.proceed() 6 println("Invocation returned") 7 return result 8 } 9}
MethodInvocation의 proceed() method에 대한 호출에 주목하십시오. 이는
interceptor chain을 따라 join point를 향해 진행합니다. 대부분의 interceptors는 이 method를 호출하고
그 return value를 반환합니다. 그러나 MethodInterceptor는 다른 around advice와 마찬가지로
proceed method를 호출하는 대신 다른 값을 반환하거나 exception을 던질 수 있습니다.
그러나 정당한 이유 없이 그렇게 하지는 않아야 합니다.
MethodInterceptorimplementations는 다른 AOP Alliance-compliant AOP implementations와의 상호 운용성을 제공합니다. 이 섹션의 나머지 부분에서 논의되는 다른 advice types는 공통 AOP 개념을 구현하지만 Spring-specific 방식으로 구현합니다. 가장 구체적인 advice type을 사용하는 데 장점이 있지만, 다른 AOP framework에서 aspect를 실행해야 할 가능성이 있다면MethodInterceptoraround advice를 사용하는 것이 좋습니다. Pointcut은 현재 framework 간에 상호 운용되지 않으며, AOP Alliance는 현재 pointcut interfaces를 정의하지 않는다는 점에 유의하십시오.
더 단순한 advice type은 _before advice_입니다. 이는 method에 들어가기 전에만 호출되므로
MethodInvocation object가 필요하지 않습니다.
Before advice의 주요 장점은 proceed()
method를 호출할 필요가 없고, 따라서 interceptor chain을 따라 진행하지 못하는
실수를 저지를 가능성이 없다는 점입니다.
다음 listing은 MethodBeforeAdvice interface를 보여 줍니다:
1public interface MethodBeforeAdvice extends BeforeAdvice { 2 3 void before(Method m, Object[] args, Object target) throws Throwable; 4}
Return type이 void인 것에 주목하십시오. Before advice는 join
point가 실행되기 전에 custom behavior를 삽입할 수 있지만 return value를 변경할 수는 없습니다.
Before advice가 exception을 던지면,
interceptor chain의 추가 실행을 중단합니다. Exception은
interceptor chain을 따라 다시 전파됩니다. 만약 그것이 unchecked이거나
invoked method의 signature에 있다면, client에 직접 전달됩니다. 그렇지 않으면,
AOP proxy에 의해 unchecked exception으로 wrapping됩니다.
다음 예제는 모든 method invocation을 count하는 Spring의 before advice를 보여 줍니다:
1public class CountingBeforeAdvice implements MethodBeforeAdvice { 2 3 private int count; 4 5 public void before(Method m, Object[] args, Object target) throws Throwable { 6 ++count; 7 } 8 9 public int getCount() { 10 return count; 11 } 12}
1class CountingBeforeAdvice : MethodBeforeAdvice { 2 3 var count: Int = 0 4 5 override fun before(m: Method, args: Array<Any>, target: Any?) { 6 ++count 7 } 8}
Before advice는 어떤 pointcut과도 함께 사용할 수 있습니다.
_Throws advice_는 join point가 exception을 던진 경우 join point의 return 이후에
호출됩니다. Spring은 typed throws advice를 제공합니다. 이는
org.springframework.aop.ThrowsAdvice interface가 어떤 method도 포함하지 않는다는 것을
의미합니다. 이는 주어진 object가 하나 이상의 typed throws
advice methods를 구현한다는 것을 식별하는 marker interface입니다. 이러한 methods는 다음과 같은 형태여야 합니다:
1afterThrowing([Method, args, target], subclassOfThrowable)
마지막 argument만 필수입니다. Advice method의 signature는 method와 arguments에 관심이 있는지 여부에 따라 하나 또는 네 개의 arguments를 가질 수 있습니다. 다음 두 listing은 throws advice의 예제인 classes를 보여 줍니다.
다음 advice는 RemoteException (및 RemoteException의 subclass)을
던지는 경우 호출됩니다:
1public class RemoteThrowsAdvice implements ThrowsAdvice { 2 3 public void afterThrowing(RemoteException ex) throws Throwable { 4 // Do something with remote exception 5 } 6}
1class RemoteThrowsAdvice : ThrowsAdvice { 2 3 fun afterThrowing(ex: RemoteException) { 4 // Do something with remote exception 5 } 6}
앞의 advice와 달리, 다음 예제는 네 개의 arguments를 선언하여
invoked method, method arguments, 그리고 target object에 접근할 수 있습니다. 다음 advice는
ServletException이 던져진 경우 호출됩니다:
1public class ServletThrowsAdviceWithArguments implements ThrowsAdvice { 2 3 public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) { 4 // Do something with all arguments 5 } 6}
1class ServletThrowsAdviceWithArguments : ThrowsAdvice { 2 3 fun afterThrowing(m: Method, args: Array<Any>, target: Any, ex: ServletException) { 4 // Do something with all arguments 5 } 6}
마지막 예제는 이 두 method가 모두 RemoteException과 ServletException을 처리하는
단일 class에서 어떻게 사용될 수 있는지를 보여 줍니다. 어떤 수의 throws advice
methods든 단일 class에 결합할 수 있습니다. 다음 listing은 마지막 예제를 보여 줍니다:
1public static class CombinedThrowsAdvice implements ThrowsAdvice { 2 3 public void afterThrowing(RemoteException ex) throws Throwable { 4 // Do something with remote exception 5 } 6 7 public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) { 8 // Do something with all arguments 9 } 10}
1class CombinedThrowsAdvice : ThrowsAdvice { 2 3 fun afterThrowing(ex: RemoteException) { 4 // Do something with remote exception 5 } 6 7 fun afterThrowing(m: Method, args: Array<Any>, target: Any, ex: ServletException) { 8 // Do something with all arguments 9 } 10}
Throws-advice method가 스스로 exception을 던지는 경우, 원래 exception을 override합니다(즉, 사용자에게 던져지는 exception을 변경합니다). Override하는 exception은 일반적으로 어떤 method signature와도 호환되는 RuntimeException입니다. 그러나 throws-advice method가 checked exception을 던지는 경우, target method의 선언된 exceptions와 일치해야 하며, 따라서 어느 정도까지는 특정 target method signatures에 결합됩니다. Target method의 signature와 호환되지 않는 선언되지 않은 checked exception을 던지지 마십시오!
Throws advice는 어떤 pointcut과도 함께 사용할 수 있습니다.
Spring의 _after returning advice_는
org.springframework.aop.AfterReturningAdvice interface를 구현해야 하며, 다음 listing이 이를 보여 줍니다:
1public interface AfterReturningAdvice extends Advice { 2 3 void afterReturning(Object returnValue, Method m, Object[] args, Object target) 4 throws Throwable; 5}
After returning advice는 return value(이를 변경할 수는 없음), invoked method, method의 arguments, 그리고 target에 접근할 수 있습니다.
다음 after returning advice는 exception을 던지지 않은 모든 성공적인 method invocation을 count합니다:
1public class CountingAfterReturningAdvice implements AfterReturningAdvice { 2 3 private int count; 4 5 public void afterReturning(Object returnValue, Method m, Object[] args, Object target) 6 throws Throwable { 7 ++count; 8 } 9 10 public int getCount() { 11 return count; 12 } 13}
1class CountingAfterReturningAdvice : AfterReturningAdvice { 2 3 var count: Int = 0 4 private set 5 6 override fun afterReturning(returnValue: Any?, m: Method, args: Array<Any>, target: Any?) { 7 ++count 8 } 9}
이 advice는 execution path를 변경하지 않습니다. 만약 exception을 던지면, return value 대신 interceptor chain을 따라 던져집니다.
After returning advice는 어떤 pointcut과도 함께 사용할 수 있습니다.
Spring은 _introduction advice_를 특별한 종류의 interception advice로 취급합니다.
Introduction에는 다음 interface를 구현하는 IntroductionAdvisor와 IntroductionInterceptor가
필요합니다:
1public interface IntroductionInterceptor extends MethodInterceptor { 2 3 boolean implementsInterface(Class intf); 4}
AOP Alliance MethodInterceptor interface로부터 상속된 invoke() method는
introduction을 구현해야 합니다. 즉, 호출된 method가 introduced
interface에 있는 경우, introduction interceptor는 method 호출을 처리할 책임이 있으며 —
proceed()를 호출할 수 없습니다.
Introduction advice는 class 수준에서 적용되며 method
수준이 아니기 때문에 어떤 pointcut과도 함께 사용할 수 없습니다. Introduction advice는
다음 method를 가진 IntroductionAdvisor와만 함께 사용할 수 있습니다:
1public interface IntroductionAdvisor extends Advisor, IntroductionInfo { 2 3 ClassFilter getClassFilter(); 4 5 void validateInterfaces() throws IllegalArgumentException; 6} 7 8public interface IntroductionInfo { 9 10 Class<?>[] getInterfaces(); 11}
MethodMatcher는 없으며, 따라서 introduction
advice와 연관된 Pointcut도 없습니다. Class filtering만이 논리적입니다.
getInterfaces() method는 이 advisor에 의해 introduced되는 interfaces를 반환합니다.
validateInterfaces() method는
introduced interfaces가 설정된 IntroductionInterceptor에 의해 구현될 수 있는지
여부를 확인하기 위해 내부적으로 사용됩니다.
Spring test suite에서 예제를 하나 생각해 보고, 다음 interface를 하나 이상의 objects에 introduce하고자 한다고 가정해 봅시다:
1public interface Lockable { 2 void lock(); 3 void unlock(); 4 boolean locked(); 5}
1interface Lockable { 2 fun lock() 3 fun unlock() 4 fun locked(): Boolean 5}
이는 mixin을 보여 줍니다. 우리는 advised objects를 그 type이 무엇이든 Lockable로
cast할 수 있기를 원하며, lock과 unlock methods를 호출할 수 있기를 원합니다. lock() method를 호출하면,
모든 setter methods가 LockedException을 던지기를 원합니다. 따라서 우리는
objects가 이에 대한 어떤 지식도 없이 immutable이 되도록 하는 능력을 제공하는 aspect를
추가할 수 있습니다. 이는 AOP의 좋은 예입니다.
먼저, 실질적인 작업을 수행하는 IntroductionInterceptor가 필요합니다. 이
경우, 우리는 org.springframework.aop.support.DelegatingIntroductionInterceptor
convenience class를 확장합니다. IntroductionInterceptor를 직접 구현할 수도 있지만,
대부분의 경우 DelegatingIntroductionInterceptor를 사용하는 것이 가장 좋습니다.
DelegatingIntroductionInterceptor는
introduced interfaces의 실제 구현에 introduction을 위임하도록 설계되어 있으며,
이를 위해 interception을 사용하는 것을 감춥니다. Constructor argument를 사용하여
delegate를 임의의 object로 설정할 수 있습니다.
기본 delegate(no-argument constructor가 사용될 때)는 this입니다. 따라서 다음 예제에서,
delegate는 DelegatingIntroductionInterceptor의 subclass인 LockMixin입니다.
Delegate(기본적으로 자기 자신)가 주어지면, DelegatingIntroductionInterceptor instance는
delegate가 구현한 모든 interfaces(
IntroductionInterceptor를 제외하고)를 찾고, 이들 중 어느 것에 대해서든 introductions를
지원합니다.
LockMixin과 같은 subclasses는 노출되어서는 안 되는 interfaces를 숨기기 위해
suppressInterface(Class intf) method를 호출할 수 있습니다. 그러나
IntroductionInterceptor가 지원할 준비가 된 interfaces가 아무리 많더라도,
실제로 어떤 interfaces가 노출되는지는 사용된
IntroductionAdvisor가 제어합니다.
Introduced interface는 target에 의한 동일한 interface의 어떤 구현도 숨깁니다.
따라서 LockMixin은 DelegatingIntroductionInterceptor를 확장하고
자체적으로 Lockable을 구현합니다. Superclass는 introduction을 위해 Lockable이
지원될 수 있다는 것을 자동으로 인식하므로,
이를 명시할 필요가 없습니다. 우리는 이런 방식으로 임의의 수의
interfaces를 introduce할 수 있습니다.
locked instance variable의 사용에 주목하십시오. 이는 target object에
보유된 state에 추가적인 state를 효과적으로 추가합니다.
다음 예제는 예제 LockMixin class를 보여 줍니다:
1public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable { 2 3 private boolean locked; 4 5 public void lock() { 6 this.locked = true; 7 } 8 9 public void unlock() { 10 this.locked = false; 11 } 12 13 public boolean locked() { 14 return this.locked; 15 } 16 17 public Object invoke(MethodInvocation invocation) throws Throwable { 18 if (locked() && invocation.getMethod().getName().indexOf("set") == 0) { 19 throw new LockedException(); 20 } 21 return super.invoke(invocation); 22 } 23}
1class LockMixin : DelegatingIntroductionInterceptor(), Lockable { 2 3 private var locked: Boolean = false 4 5 fun lock() { 6 this.locked = true 7 } 8 9 fun unlock() { 10 this.locked = false 11 } 12 13 fun locked(): Boolean { 14 return this.locked 15 } 16 17 override fun invoke(invocation: MethodInvocation): Any? { 18 if (locked() && invocation.method.name.indexOf("set") == 0) { 19 throw LockedException() 20 } 21 return super.invoke(invocation) 22 } 23}
대부분의 경우 invoke() method를 override할 필요가 없습니다.
DelegatingIntroductionInterceptor 구현은(만약 method가 introduced된 것이라면 delegate method를 호출하고,
그렇지 않으면 join point를 향해 진행합니다)
일반적으로 충분합니다. 현재의 경우, 우리는 하나의 check를 추가해야 합니다. locked mode인 경우
어떤 setter method도 호출될 수 없습니다.
필요한 introduction은 별도의
LockMixin instance를 보유하고 introduced interfaces(이 경우에는
Lockable만)를 지정하기만 하면 됩니다. 더 복잡한 예제에서는
(prototype으로 정의될) introduction interceptor에 대한 reference를 받을 수 있습니다. 이 경우,
LockMixin에 관련된 configuration이 없으므로 new를 사용하여 생성합니다.
다음 예제는 우리의 LockMixinAdvisor class를 보여 줍니다:
1public class LockMixinAdvisor extends DefaultIntroductionAdvisor { 2 3 public LockMixinAdvisor() { 4 super(new LockMixin(), Lockable.class); 5 } 6}
1class LockMixinAdvisor : DefaultIntroductionAdvisor(LockMixin(), Lockable::class.java)
이 advisor는 configuration이 필요 없기 때문에 매우 간단히 적용할 수 있습니다. (그러나
IntroductionAdvisor 없이 IntroductionInterceptor를 사용하는 것은
불가능합니다.) Introductions에서 일반적으로 그러하듯이, advisor는 stateful하므로
per-instance여야 합니다. 우리는 각 advised object마다 서로 다른 LockMixinAdvisor instance,
따라서 LockMixin이 필요합니다. Advisor는 advised object state의 일부를
구성합니다.
우리는 이 advisor를 Advised.addAdvisor() method를 사용하여 프로그래밍 방식으로 적용하거나,
(권장되는 방법으로) 다른 advisor와 마찬가지로 XML configuration에서 적용할 수 있습니다. 아래에서 논의되는
모든 proxy 생성 선택 사항들(“auto proxy creators” 포함)은 introductions와 stateful mixins을
정확하게 처리합니다.
Pointcut API in Spring
The Advisor API in Spring