Loading...
Spring Framework Reference Documentation 7.0.2의 Schema-based AOP Support의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
XML 기반 format을 선호하는 경우, Spring은 aop namespace tag를 사용하여 aspect를 정의하는 것도 지원합니다. @AspectJ style을 사용할 때와 정확히 동일한 pointcut expression과 advice 종류가 지원됩니다. 따라서 이 섹션에서는 해당 syntax에 초점을 맞추고, pointcut expression을 작성하고 advice parameter를 binding하는 방법을 이해하기 위해 이전 섹션( @AspectJ support)의 논의를 참조합니다.
이 섹션에서 설명하는 aop namespace tag를 사용하려면, XML Schema-based configuration에 설명된 대로 spring-aop schema를 import해야 합니다. aop namespace에서 tag를 import하는 방법은 the AOP schema 를 참조하십시오.
Spring configuration 내에서, 모든 aspect와 advisor element는 <aop:config> element 안에 위치해야 합니다 (application context configuration 안에 하나 이상의 <aop:config> element를 가질 수 있습니다). <aop:config> element는 pointcut, advisor, aspect element를 포함할 수 있습니다 (이들은 반드시 해당 순서로 선언되어야 함에 유의하십시오).
<aop:config>style의 configuration은 Spring의 auto-proxying mechanism을 많이 사용합니다. 이는 이미BeanNameAutoProxyCreator또는 유사한 것을 사용하여 명시적으로 auto-proxying을 사용하는 경우, advice가 weaving되지 않는 등의 문제를 일으킬 수 있습니다. 권장되는 사용 패턴은<aop:config>style만 사용하거나AutoProxyCreatorstyle만 사용하고, 절대 둘을 섞지 않는 것입니다.
schema support를 사용할 때, aspect는 Spring application context에 bean으로 정의된 일반 Java object입니다. state와 behavior는 object의 field와 method에 캡처되고, pointcut과 advice 정보는 XML에 캡처됩니다.
다음 예제에서 보듯이 <aop:aspect> element를 사용하여 aspect를 선언하고, ref attribute를 사용하여 backing bean을 참조할 수 있습니다:
1<aop:config> 2 <aop:aspect id="myAspect" ref="aBean"> 3 ... 4 </aop:aspect> 5</aop:config> 6 7<bean id="aBean" class="..."> 8 ... 9</bean>
aspect를 backing하는 bean(이 경우 aBean)은 물론 다른 어떤 Spring bean처럼 configuration과 dependency injection을 받을 수 있습니다.
_named pointcut_을 <aop:config> element 안에 선언하여, pointcut definition을 여러 aspect와 advisor에서 공유할 수 있습니다.
service layer 내의 어떤 business service의 execution을 나타내는 pointcut은 다음과 같이 정의할 수 있습니다:
1<aop:config> 2 3 <aop:pointcut id="businessService" 4 expression="execution(* com.xyz.service.*.*(..))" /> 5 6</aop:config>
pointcut expression 자체는 @AspectJ support에 설명된 것과 동일한 AspectJ pointcut expression language를 사용한다는 점에 유의하십시오. schema based declaration style을 사용하는 경우, pointcut expression 내에서 @Aspect type에 정의된 _named pointcut_을 참조할 수도 있습니다.
따라서 위의 pointcut을 정의하는 또 다른 방법은 다음과 같습니다:
1<aop:config> 2 3 <aop:pointcut id="businessService" 4 expression="com.xyz.CommonPointcuts.businessService()" /> <!-- (1) --> 5 6</aop:config>
| 1 | Sharing Named Pointcut Definitions에 정의된 businessService named pointcut을 참조합니다. |
aspect _내부_에 pointcut을 선언하는 것은 다음 예제에서 보듯이 top-level pointcut을 선언하는 것과 매우 유사합니다:
1<aop:config> 2 3 <aop:aspect id="myAspect" ref="aBean"> 4 5 <aop:pointcut id="businessService" 6 expression="execution(* com.xyz.service.*.*(..))"/> 7 8 ... 9 </aop:aspect> 10 11</aop:config>
@AspectJ aspect와 거의 동일한 방식으로, schema based definition style로 선언된 pointcut은 join point context를 수집할 수 있습니다. 예를 들어, 다음 pointcut은 join point context로 this object를 수집하고 이를 advice에 전달합니다:
1<aop:config> 2 3 <aop:aspect id="myAspect" ref="aBean"> 4 5 <aop:pointcut id="businessService" 6 expression="execution(* com.xyz.service.*.*(..)) && this(service)"/> 7 8 <aop:before pointcut-ref="businessService" method="monitor"/> 9 10 ... 11 </aop:aspect> 12 13</aop:config>
advice는 다음과 같이 일치하는 이름의 parameter를 포함하여, 수집된 join point context를 받을 수 있도록 선언되어야 합니다:
1public void monitor(Object service) { 2 // ... 3}
1fun monitor(service: Any) { 2 // ... 3}
pointcut sub-expression을 결합할 때, XML document 내에서 &&는 다루기 불편하므로, &&, ||, ! 대신 각각 and, or, not keyword를 사용할 수 있습니다. 예를 들어, 이전 pointcut은 다음과 같이 더 잘 작성할 수 있습니다:
1<aop:config> 2 3 <aop:aspect id="myAspect" ref="aBean"> 4 5 <aop:pointcut id="businessService" 6 expression="execution(* com.xyz.service.*.*(..)) and this(service)"/> 7 8 <aop:before pointcut-ref="businessService" method="monitor"/> 9 10 ... 11 </aop:aspect> 12 13</aop:config>
이 방식으로 정의된 pointcut은 XML id로 참조되며, composite pointcut을 구성하기 위한 named pointcut으로 사용할 수 없다는 점에 유의하십시오. 따라서 schema-based definition style에서의 named pointcut 지원은 @AspectJ style에서 제공되는 것보다 더 제한적입니다.
schema-based AOP support는 @AspectJ style과 동일한 다섯 가지 종류의 advice를 사용하며, 의미도 완전히 동일합니다.
Before advice는 일치하는 method execution 전에 실행됩니다. 다음 예제에서 보듯이 <aop:aspect> 안에서 <aop:before> element를 사용하여 선언합니다:
1<aop:aspect id="beforeExample" ref="aBean"> 2 3 <aop:before 4 pointcut-ref="dataAccessOperation" 5 method="doAccessCheck"/> 6 7 ... 8 9</aop:aspect>
위 예제에서 dataAccessOperation은 top (<aop:config>) level에서 정의된 _named pointcut_의 id입니다 ( Declaring a Pointcut를 참조하십시오).
@AspectJ style에 대한 논의에서 언급했듯이, _named pointcut_을 사용하면 코드의 가독성이 크게 향상될 수 있습니다. 자세한 내용은 Sharing Named Pointcut Definitions를 참조하십시오.
대신 pointcut을 inline으로 정의하려면, pointcut-ref attribute를 pointcut attribute로 교체하면 됩니다:
1<aop:aspect id="beforeExample" ref="aBean"> 2 3 <aop:before 4 pointcut="execution(* com.xyz.dao.*.*(..))" 5 method="doAccessCheck"/> 6 7 ... 8 9</aop:aspect>
method attribute는 advice의 body를 제공하는 method(doAccessCheck)를 식별합니다. 이 method는 advice를 포함하는 aspect element가 참조하는 bean에 대해 정의되어야 합니다. data access operation이 수행되기 전에(pointcut expression과 일치하는 method execution join point), aspect bean의 doAccessCheck method가 호출됩니다.
After returning advice는 일치하는 method execution이 정상적으로 완료될 때 실행됩니다. <aop:aspect> 안에서 before advice와 동일한 방식으로 선언됩니다. 다음 예제는 이를 선언하는 방법을 보여줍니다:
1<aop:aspect id="afterReturningExample" ref="aBean"> 2 3 <aop:after-returning 4 pointcut="execution(* com.xyz.dao.*.*(..))" 5 method="doAccessCheck"/> 6 7 ... 8</aop:aspect>
@AspectJ style과 마찬가지로, advice body 내에서 return value를 받을 수 있습니다. 이를 위해, 다음 예제에서 보듯이 returning attribute를 사용하여 return value가 전달되어야 하는 parameter의 이름을 지정합니다:
1<aop:aspect id="afterReturningExample" ref="aBean"> 2 3 <aop:after-returning 4 pointcut="execution(* com.xyz.dao.*.*(..))" 5 returning="retVal" 6 method="doAccessCheck"/> 7 8 ... 9</aop:aspect>
doAccessCheck method는 retVal이라는 이름의 parameter를 선언해야 합니다. 이 parameter의 type은 @AfterReturning에 대해 설명된 것과 동일한 방식으로 matching을 제한합니다. 예를 들어, method signature는 다음과 같이 선언할 수 있습니다:
1public void doAccessCheck(Object retVal) { 2 ... 3}
1fun doAccessCheck(retVal: Any) { 2 ... 3}
After throwing advice는 일치하는 method execution이 exception을 던지며 종료될 때 실행됩니다. 다음 예제에서 보듯이, <aop:aspect> 안에서 after-throwing element를 사용하여 선언합니다:
1<aop:aspect id="afterThrowingExample" ref="aBean"> 2 3 <aop:after-throwing 4 pointcut="execution(* com.xyz.dao.*.*(..))" 5 method="doRecoveryActions"/> 6 7 ... 8</aop:aspect>
@AspectJ style과 마찬가지로, advice body 내에서 던져진 exception을 받을 수 있습니다. 이를 위해, 다음 예제에서 보듯이 throwing attribute를 사용하여 exception이 전달되어야 하는 parameter의 이름을 지정합니다:
1<aop:aspect id="afterThrowingExample" ref="aBean"> 2 3 <aop:after-throwing 4 pointcut="execution(* com.xyz.dao.*.*(..))" 5 throwing="dataAccessEx" 6 method="doRecoveryActions"/> 7 8 ... 9</aop:aspect>
doRecoveryActions method는 dataAccessEx라는 이름의 parameter를 선언해야 합니다. 이 parameter의 type은 @AfterThrowing에 대해 설명된 것과 동일한 방식으로 matching을 제한합니다. 예를 들어, method signature는 다음과 같이 선언할 수 있습니다:
1public void doRecoveryActions(DataAccessException dataAccessEx) { 2 ... 3}
1fun doRecoveryActions(dataAccessEx: DataAccessException) { 2 ... 3}
After (finally) advice는 일치하는 method execution이 어떻게 종료되든 항상 실행됩니다. 다음 예제에서 보듯이 after element를 사용하여 선언할 수 있습니다:
1<aop:aspect id="afterFinallyExample" ref="aBean"> 2 3 <aop:after 4 pointcut="execution(* com.xyz.dao.*.*(..))" 5 method="doReleaseLock"/> 6 7 ... 8</aop:aspect>
마지막 종류의 advice는 around advice입니다. Around advice는 일치하는 method execution을 "둘러싸고" 실행됩니다. method가 실행되기 전과 후에 작업을 수행할 수 있는 기회를 가지며, method가 실제로 언제, 어떻게, 심지어 실행될지 여부까지도 결정할 수 있습니다.
Around advice는 thread-safe한 방식으로 method execution 전후에 state를 공유해야 할 때(예: timer를 시작하고 중지하는 경우) 자주 사용됩니다.
요구 사항을 충족하는 가장 약한 형태의 advice만 사용하십시오. 예를 들어, before advice로 충분한 경우 around advice를 사용하지 마십시오.
aop:around element를 사용하여 around advice를 선언할 수 있습니다. advice method는 return type으로 Object를 선언해야 하며, method의 첫 번째 parameter는 ProceedingJoinPoint type이어야 합니다. advice method의 body 내에서, underlying method가 실행되도록 ProceedingJoinPoint에 대해 proceed()를 호출해야 합니다.
argument 없이 proceed()를 호출하면, 호출자의 original argument가 underlying method가 호출될 때 전달됩니다. 고급 use case의 경우, argument array(Object[])를 받는 proceed() method의 overloaded variant가 있습니다. array 내의 값은 underlying method가 호출될 때 argument로 사용됩니다. Object[]와 함께 proceed를 호출하는 것에 대한 내용은 Around Advice를 참조하십시오.
다음 예제는 XML에서 around advice를 선언하는 방법을 보여줍니다:
1<aop:aspect id="aroundExample" ref="aBean"> 2 3 <aop:around 4 pointcut="execution(* com.xyz.service.*.*(..))" 5 method="doBasicProfiling"/> 6 7 ... 8</aop:aspect>
doBasicProfiling advice의 구현은 (물론 annotation을 제외하고) @AspectJ 예제와 정확히 동일할 수 있습니다. 다음 예제가 이를 보여줍니다:
1public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { 2 // start stopwatch 3 Object retVal = pjp.proceed(); 4 // stop stopwatch 5 return retVal; 6}
1fun doBasicProfiling(pjp: ProceedingJoinPoint): Any? { 2 // start stopwatch 3 val retVal = pjp.proceed() 4 // stop stopwatch 5 return pjp.proceed() 6}
schema-based declaration style은 @AspectJ support에 대해 설명된 것과 동일한 방식으로, pointcut parameter를 이름으로 advice method parameter와 matching하여 fully typed advice를 지원합니다. 자세한 내용은 Advice Parameters를 참조하십시오.
앞서 설명한 detection strategy에 의존하지 않고 advice method의 argument 이름을 명시적으로 지정하려는 경우, advice element의 arg-names attribute를 사용하여 지정할 수 있으며, 이는 advice annotation의 argNames attribute와 동일한 방식으로 처리됩니다( Determining Argument Names에 설명된 대로). 다음 예제는 XML에서 argument 이름을 지정하는 방법을 보여줍니다:
1<aop:before 2 pointcut="com.xyz.Pointcuts.publicMethod() and @annotation(auditable)" <!-- (1) --> 3 method="audit" 4 arg-names="auditable" />
| 1 | Combining Pointcut Expressions에 정의된 publicMethod named pointcut을 참조합니다. |
arg-names attribute는 comma로 구분된 parameter 이름 목록을 허용합니다.
다음은 XSD-based approach의 약간 더 복잡한 예로, 여러 strongly typed parameter와 함께 사용되는 around advice를 보여줍니다:
1package com.xyz.service; 2 3public interface PersonService { 4 5 Person getPerson(String personName, int age); 6} 7 8public class DefaultPersonService implements PersonService { 9 10 public Person getPerson(String name, int age) { 11 return new Person(name, age); 12 } 13}
1package com.xyz.service 2 3interface PersonService { 4 5 fun getPerson(personName: String, age: Int): Person 6} 7 8class DefaultPersonService : PersonService { 9 10 fun getPerson(name: String, age: Int): Person { 11 return Person(name, age) 12 } 13}
다음은 aspect입니다. profile(..) method가 여러 strongly-typed parameter를 받으며, 그중 첫 번째 parameter가 method 호출을 진행하는 데 사용되는 join point라는 사실에 주목하십시오. 이 parameter의 존재는 다음 예제에서 보듯이 profile(..)이 around advice로 사용된다는 표시입니다:
1package com.xyz; 2 3import org.aspectj.lang.ProceedingJoinPoint; 4import org.springframework.util.StopWatch; 5 6public class SimpleProfiler { 7 8 public Object profile(ProceedingJoinPoint call, String name, int age) throws Throwable { 9 StopWatch clock = new StopWatch("Profiling for '" + name + "' and '" + age + "'"); 10 try { 11 clock.start(call.toShortString()); 12 return call.proceed(); 13 } finally { 14 clock.stop(); 15 System.out.println(clock.prettyPrint()); 16 } 17 } 18}
1package com.xyz 2 3import org.aspectj.lang.ProceedingJoinPoint 4import org.springframework.util.StopWatch 5 6class SimpleProfiler { 7 8 fun profile(call: ProceedingJoinPoint, name: String, age: Int): Any? { 9 val clock = StopWatch("Profiling for '$name' and '$age'") 10 try { 11 clock.start(call.toShortString()) 12 return call.proceed() 13 } finally { 14 clock.stop() 15 println(clock.prettyPrint()) 16 } 17 } 18}
마지막으로, 다음 예제 XML configuration은 앞선 advice가 특정 join point에 대해 실행되도록 합니다:
1<beans xmlns="http://www.springframework.org/schema/beans" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns:aop="http://www.springframework.org/schema/aop" 4 xsi:schemaLocation=" 5 http://www.springframework.org/schema/beans 6 https://www.springframework.org/schema/beans/spring-beans.xsd 7 http://www.springframework.org/schema/aop 8 https://www.springframework.org/schema/aop/spring-aop.xsd"> 9 10 <!-- this is the object that will be proxied by Spring's AOP infrastructure --> 11 <bean id="personService" class="com.xyz.service.DefaultPersonService"/> 12 13 <!-- this is the actual advice itself --> 14 <bean id="profiler" class="com.xyz.SimpleProfiler"/> 15 16 <aop:config> 17 <aop:aspect ref="profiler"> 18 19 <aop:pointcut id="theExecutionOfSomePersonServiceMethod" 20 expression="execution(* com.xyz.service.PersonService.getPerson(String,int)) 21 and args(name, age)"/> 22 23 <aop:around pointcut-ref="theExecutionOfSomePersonServiceMethod" 24 method="profile"/> 25 26 </aop:aspect> 27 </aop:config> 28 29</beans>
다음 driver script를 고려해 보십시오:
1public class Boot { 2 3 public static void main(String[] args) { 4 ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); 5 PersonService person = ctx.getBean(PersonService.class); 6 person.getPerson("Pengo", 12); 7 } 8}
1fun main() { 2 val ctx = ClassPathXmlApplicationContext("beans.xml") 3 val person = ctx.getBean(PersonService.class) 4 person.getPerson("Pengo", 12) 5}
이러한 Boot class를 사용하면, 표준 출력에 다음과 유사한 output을 얻게 됩니다:
1StopWatch 'Profiling for 'Pengo' and '12': running time (millis) = 0 2----------------------------------------- 3ms % Task name 4----------------------------------------- 500000 ? execution(getFoo)
여러 advice가 동일한 join point(executing method)에서 실행되어야 할 때, ordering 규칙은
Advice Ordering에 설명된 것과 같습니다. aspect 간의 precedence는 <aop:aspect> element의 order attribute 또는 aspect를 backing하는 bean에 @Order annotation을 추가하거나 bean이 Ordered interface를 구현하게 함으로써 결정됩니다.
동일한
@Aspectclass에 정의된 advice method에 대한 precedence 규칙과는 달리, 동일한<aop:aspect>element에 정의된 두 개의 advice가 동일한 join point에서 모두 실행되어야 하는 경우, precedence는 둘러싸고 있는<aop:aspect>element 내에서 advice element가 선언된 순서에 의해 결정되며, 선언 순서가 높을수록 precedence가 높습니다. 예를 들어, 동일한<aop:aspect>element에 정의된aroundadvice와beforeadvice가 동일한 join point에 적용되는 경우,aroundadvice가beforeadvice보다 높은 precedence를 갖도록 하려면<aop:around>element가<aop:before>element보다 먼저 선언되어야 합니다. 일반적인 경험법칙으로, 동일한<aop:aspect>element에 정의된 여러 advice가 동일한 join point에 적용되는 경우, 각<aop:aspect>element마다 join point당 하나의 advice method로 이러한 advice method를 통합하거나, advice를 별도의<aop:aspect>element로 리팩터링하여 aspect level에서 순서를 지정하는 것을 고려하십시오.
Introduction(AspectJ에서는 inter-type declaration으로 알려져 있음)은 aspect가 advised object가 주어진 interface를 구현한다고 선언하고, 해당 object를 대신하여 그 interface의 구현을 제공할 수 있게 해줍니다.
aop:aspect 안에서 aop:declare-parents element를 사용하여 introduction을 만들 수 있습니다.
aop:declare-parents element를 사용하여 matching type이 새로운 parent를 갖도록 선언할 수 있습니다(따라서 이름이 이렇게 붙었습니다).
예를 들어, UsageTracked라는 이름의 interface와 DefaultUsageTracked라는 이름의 해당 interface 구현이 주어졌을 때, 다음 aspect는 service interface의 모든 implementor가 UsageTracked interface도 구현한다고 선언합니다. (예를 들어 JMX를 통해 통계를 노출하기 위해.)
1<aop:aspect id="usageTrackerAspect" ref="usageTracking"> 2 3 <aop:declare-parents 4 types-matching="com.xyz.service.*+" 5 implement-interface="com.xyz.service.tracking.UsageTracked" 6 default-impl="com.xyz.service.tracking.DefaultUsageTracked"/> 7 8 <aop:before 9 pointcut="execution(* com.xyz..service.*.*(..)) 10 and this(usageTracked)" 11 method="recordUsage"/> 12 13</aop:aspect>
usageTracking bean을 backing하는 class에는 다음 method가 포함됩니다:
1public void recordUsage(UsageTracked usageTracked) { 2 usageTracked.incrementUseCount(); 3}
1fun recordUsage(usageTracked: UsageTracked) { 2 usageTracked.incrementUseCount() 3}
구현할 interface는 implement-interface attribute에 의해 결정됩니다.
types-matching attribute의 값은 AspectJ type pattern입니다. matching type의 어떤 bean이든 UsageTracked interface를 구현합니다. 앞선 예제의 before advice에서 보듯이, service bean은 UsageTracked interface의 구현으로 직접 사용할 수 있습니다.
bean에 programmatically 접근하려면 다음과 같이 작성할 수 있습니다:
1UsageTracked usageTracked = context.getBean("myService", UsageTracked.class);
1val usageTracked = context.getBean("myService", UsageTracked.class)
schema-defined aspect에 대해 지원되는 유일한 instantiation model은 singleton model입니다. 다른 instantiation model은 향후 릴리스에서 지원될 수 있습니다.
"advisors" 개념은 Spring에 정의된 AOP support에서 나왔으며, AspectJ에는 직접적인 동등 개념이 없습니다. advisor는 하나의 advice를 가진 작고 self-contained한 aspect와 같습니다. advice 자체는 bean으로 표현되며, Advice Types in Spring에 설명된 advice interface 중 하나를 구현해야 합니다. Advisor는 AspectJ pointcut expression을 활용할 수 있습니다.
Spring은 <aop:advisor> element로 advisor 개념을 지원합니다. 이는 transaction advice와 함께 사용되는 경우가 가장 많으며, transaction advice는 Spring에서 자체적인 namespace support도 가지고 있습니다. 다음 예제는 advisor를 보여줍니다:
1<aop:config> 2 3 <aop:pointcut id="businessService" 4 expression="execution(* com.xyz.service.*.*(..))"/> 5 6 <aop:advisor 7 pointcut-ref="businessService" 8 advice-ref="tx-advice" /> 9 10</aop:config> 11 12<tx:advice id="tx-advice"> 13 <tx:attributes> 14 <tx:method name="*" propagation="REQUIRED"/> 15 </tx:attributes> 16</tx:advice>
앞선 예제에서 사용된 pointcut-ref attribute뿐만 아니라, pointcut attribute를 사용하여 pointcut expression을 inline으로 정의할 수도 있습니다.
advisor의 precedence를 정의하여 advice가 ordering에 참여하도록 하려면, order attribute를 사용하여 advisor의 Ordered 값을 정의하십시오.
이 섹션에서는 An AOP Example의 concurrent locking failure retry 예제가 schema support로 다시 작성되었을 때 어떻게 보이는지 보여줍니다.
Business service의 execution은 때때로 concurrency issue(예: deadlock loser)로 인해 실패할 수 있습니다. operation을 다시 시도하면, 다음 시도에서 성공할 가능성이 높습니다. 이러한 조건에서 다시 시도하는 것이 적절한 business service(idempotent operation으로, 충돌 해결을 위해 사용자에게 다시 돌아갈 필요가 없는 경우)의 경우, client가 PessimisticLockingFailureException을 보지 않도록 operation을 투명하게 재시도하고자 합니다.
이는 service layer의 여러 service에 걸쳐 명백히 cross-cutting되는 요구 사항이며, 따라서 aspect를 통해 구현하기에 이상적입니다.
operation을 다시 시도하려면, proceed를 여러 번 호출할 수 있도록 around advice를 사용해야 합니다. 다음 listing은 기본 aspect 구현을 보여줍니다(이는 schema support를 사용하는 일반 Java class입니다):
1public class ConcurrentOperationExecutor implements Ordered { 2 3 private static final int DEFAULT_MAX_RETRIES = 2; 4 5 private int maxRetries = DEFAULT_MAX_RETRIES; 6 private int order = 1; 7 8 public void setMaxRetries(int maxRetries) { 9 this.maxRetries = maxRetries; 10 } 11 12 public int getOrder() { 13 return this.order; 14 } 15 16 public void setOrder(int order) { 17 this.order = order; 18 } 19 20 public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable { 21 int numAttempts = 0; 22 PessimisticLockingFailureException lockFailureException; 23 do { 24 numAttempts++; 25 try { 26 return pjp.proceed(); 27 } 28 catch(PessimisticLockingFailureException ex) { 29 lockFailureException = ex; 30 } 31 } while(numAttempts <= this.maxRetries); 32 throw lockFailureException; 33 } 34}
1class ConcurrentOperationExecutor : Ordered { 2 3 private val DEFAULT_MAX_RETRIES = 2 4 5 private var maxRetries = DEFAULT_MAX_RETRIES 6 private var order = 1 7 8 fun setMaxRetries(maxRetries: Int) { 9 this.maxRetries = maxRetries 10 } 11 12 override fun getOrder(): Int { 13 return this.order 14 } 15 16 fun setOrder(order: Int) { 17 this.order = order 18 } 19 20 fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? { 21 var numAttempts = 0 22 var lockFailureException: PessimisticLockingFailureException 23 do { 24 numAttempts++ 25 try { 26 return pjp.proceed() 27 } catch (ex: PessimisticLockingFailureException) { 28 lockFailureException = ex 29 } 30 31 } while (numAttempts <= this.maxRetries) 32 throw lockFailureException 33 } 34}
aspect는 Ordered interface를 구현하여, transaction advice보다 aspect의 precedence를 더 높게 설정할 수 있다는 점에 주목하십시오(재시도할 때마다 새로운 transaction을 원합니다). maxRetries와 order property는 모두 Spring에 의해 configuration됩니다.
주요 동작은 doConcurrentOperation around advice method에서 발생합니다. proceed를 시도합니다. PessimisticLockingFailureException으로 실패하면, retry 횟수를 모두 소진하지 않은 경우 다시 시도합니다.
이 class는 @AspectJ 예제에서 사용된 class와 동일하지만, annotation이 제거되어 있습니다.
해당 Spring configuration은 다음과 같습니다:
1<aop:config> 2 3 <aop:aspect id="concurrentOperationRetry" ref="concurrentOperationExecutor"> 4 5 <aop:pointcut id="idempotentOperation" 6 expression="execution(* com.xyz.service.*.*(..))"/> 7 8 <aop:around 9 pointcut-ref="idempotentOperation" 10 method="doConcurrentOperation"/> 11 12 </aop:aspect> 13 14</aop:config> 15 16<bean id="concurrentOperationExecutor" 17 class="com.xyz.service.impl.ConcurrentOperationExecutor"> 18 <property name="maxRetries" value="3"/> 19 <property name="order" value="100"/> 20</bean>
당분간은 모든 business service가 idempotent라고 가정한다는 점에 주목하십시오. 그렇지 않은 경우, Idempotent annotation을 도입하고, service operation의 구현을 annotation으로 표시하여 실제로 idempotent한 operation만 재시도하도록 aspect를 정교화할 수 있습니다. 다음 예제는 이를 보여줍니다:
1@Retention(RetentionPolicy.RUNTIME) 2// marker annotation 3public @interface Idempotent { 4}
1@Retention(AnnotationRetention.RUNTIME) 2// marker annotation 3annotation class Idempotent
Aspect를 변경하여 idempotent operation만 재시도하도록 하려면, pointcut expression을 정교화하여 @Idempotent operation만 일치하도록 하면 됩니다. 다음과 같습니다:
1<aop:pointcut id="idempotentOperation" 2 expression="execution(* com.xyz.service.*.*(..)) and 3 @annotation(com.xyz.service.Idempotent)"/>
An AOP Example
Choosing which AOP Declaration Style to Use