XML 기반 형식을 선호한다면 Spring은 aop
네임스페이스 태그를 사용하여 측면을 정의하는 지원도 제공합니다. @AspectJ
스타일을 사용할 때와 똑같은 포인트컷 표현식과 advice 종류가 지원됩니다. 따라서 이 섹션에서는 해당 syntax에 중점을 두고 포인트컷 표현식 작성 및 advice 매개변수 바인딩에 대한 이해를 위해 이전 섹션(@AspectJ 지원)의 논의를 독자에게 안내합니다.
이 섹션에 설명된 aop 네임스페이스 태그를 사용하려면 XML 스키마 기반 구성에 설명된 대로 spring-aop
스키마를 가져와야 합니다. aop
네임스페이스에서 태그를 가져오는 방법은 AOP 스키마를 참조하세요.
Spring 구성 내에서 모든 aspect 및 advisor 요소는 <aop:config>
요소 내에 배치되어야 합니다(애플리케이션 컨텍스트 구성에 하나 이상의 <aop:config>
요소가 있을 수 있음). <aop:config>
요소는 pointcut, advisor, aspect 요소를 포함할 수 있습니다(이 순서대로 선언해야 함).
[Warning]
<aop:config>
스타일의 구성은 Spring의 자동 프록시 메커니즘을 많이 사용합니다. 이미BeanNameAutoProxyCreator
또는 이와 유사한 것을 사용하여 명시적인 자동 프록시를 사용하는 경우 이로 인해 문제(예: 짜여지지 않은 조언)가 발생할 수 있습니다. 권장되는 사용 패턴은<aop:config>
스타일만 사용하거나AutoProxyCreator
스타일만 사용하고 절대 혼합하지 않는 것입니다.
스키마 지원을 사용할 때 aspect은 Spring 애플리케이션 컨텍스트에서 Bean으로 정의된 일반 Java 객체입니다. 상태와 동작은 객체의 필드와 메소드에 캡처되고 포인트컷과 advice 정보는 XML에 캡처됩니다.
다음 예제와 같이 <aop:aspect>
요소를 사용하여aspect을 선언하고 ref
속성을 사용하여 지원 Bean을 참조할 수 있습니다.
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>
물론 aspect을 지원하는 Bean(이 경우 aBean
)은 다른 Spring Bean과 마찬가지로 구성하고 종속성을 주입할 수 있습니다.
<aop:config>
요소 내부에 명명된 포인트컷을 선언하여 포인트컷 정의가 여러 aspect과 어드바이저에서 공유되도록 할 수 있습니다.
서비스 계층에서 비즈니스 서비스의 실행을 나타내는 포인트컷은 다음과 같이 정의할 수 있습니다.
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.service.*.*(..))" />
</aop:config>
포인트컷 표현 자체는 @AspectJ 지원에 설명된 것과 동일한 AspectJ 포인트컷 표현 언어를 사용한다는 점에 유의하세요. 스키마 기반 선언 스타일을 사용하는 경우 포인트컷 표현식 내의 @Aspect
type에 정의된 명명된 포인트컷을 참조할 수도 있습니다. 따라서 위의 포인트컷을 정의하는 또 다른 방법은 다음과 같습니다.
<aop:config>
<aop:pointcut id="businessService"
expression="com.xyz.CommonPointcuts.businessService()" /> <!--(1)-->
</aop:config>
(1) 명명된 Pointcut 정의 공유에 정의된 pointcut이라는 이름의 businessService
를 참조합니다.
aspect 내부에 포인트컷을 선언하는 것은 다음 예제에서 볼 수 있듯이 최상위 수준 포인트컷을 선언하는 것과 매우 유사합니다.
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.service.*.*(..))"/>
...
</aop:aspect>
</aop:config>
@AspectJ 관점과 거의 같은 방식으로, 스키마 기반 정의 스타일을 사용하여 선언된 포인트컷은 조인 포인트 컨텍스트를 수집할 수 있습니다. 예를 들어, 다음 포인트컷은 조인 포인트 컨텍스트로 this
객체를 수집하고 이를 어드바이스에 전달합니다.
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.service.*.*(..)) && this(service)"/>
<aop:before pointcut-ref="businessService" method="monitor"/>
...
</aop:aspect>
</aop:config>
다음과 같이 일치하는 이름의 매개 변수를 포함하여 수집된 조인 포인트 컨텍스트를 수신하도록 advice을 선언해야 합니다.
public void monitor(Object service) {
// ...
}
포인트컷 하위 표현식을 결합할 때 &&
는 XML 문서 내에서 어색하므로 &&
, ||
및 !
대신 각각 and
, or
및 not
키워드를 사용할 수 있습니다. 예를 들어 이전 포인트컷은 다음과 같이 더 잘 작성할 수 있습니다.
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.service.*.*(..)) and this(service)"/>
<aop:before pointcut-ref="businessService" method="monitor"/>
...
</aop:aspect>
</aop:config>
이런 방식으로 정의된 포인트컷은 XML id
로 참조되며 복합 포인트컷을 형성하기 위해 명명된 포인트컷으로 사용될 수 없습니다. 따라서 스키마 기반 정의 스타일의 명명된 포인트컷 지원은 @AspectJ 스타일에서 제공하는 것보다 더 제한적입니다.
스키마 기반 AOP 지원은 @AspectJ 스타일과 동일한 5가지 종류의 조언을 사용하며 정확히 동일한 의미를 갖습니다.
일치하는 메서드 실행 전에 조언이 실행되기 전입니다. 다음 예제와 같이 <aop:before>
요소를 사용하여 <aop:aspect>
내부에 선언됩니다.
<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
</aop:aspect>
위의 예에서 dataAccessOperation
은 최상위(<aop:config>
) 레벨에서 정의된 명명된 포인트컷의 id
입니다(포인트컷 선언 참조).
[Note]
@AspectJ 스타일에 대한 논의에서 언급했듯이 명명된 포인트컷을 사용하면 코드의 가독성을 크게 향상시킬 수 있습니다. 자세한 내용은 명명된 Pointcut 정의 공유를 참조하세요.
대신에 포인트컷을 인라인으로 정의하려면 다음과 같이 pointcut-ref
속성을 pointcut
속성으로 바꾸십시오.
<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut="execution(* com.xyz.dao.*.*(..))"
method="doAccessCheck"/>
...
</aop:aspect>
method
속성은 advice의 본문을 제공하는 메소드(doAccessCheck
)를 식별합니다. 이 메소드는 조언을 포함하는 aspect 요소가 참조하는 Bean에 대해 정의되어야 합니다. 데이터 액세스 작업(포인트컷 표현식과 일치하는 메서드 실행 조인 포인트)이 수행되기 전에 aspect 빈의 doAccessCheck
메서드가 호출됩니다.
advice을 반환한 후 일치하는 메서드 실행이 정상적으로 완료되면 실행됩니다. 이전 advice과 같은 방식으로 <aop:aspect>
내부에 선언됩니다. 다음 예에서는 이를 선언하는 방법을 보여줍니다.
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut="execution(* com.xyz.dao.*.*(..))"
method="doAccessCheck"/>
...
</aop:aspect>
@AspectJ 스타일과 마찬가지로 advice 본문 내에서 반환 값을 얻을 수 있습니다. 이렇게 하려면 다음 예제와 같이 returning
특성(attribute)을 사용하여 반환 값이 전달되어야 하는 매개 변수의 이름을 지정합니다.
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut="execution(* com.xyz.dao.*.*(..))"
returning="retVal"
method="doAccessCheck"/>
...
</aop:aspect>
doAccessCheck
메소드는 retVal
이라는 매개변수를 선언해야 합니다. 이 매개변수의 type은 @AfterReturning
에 대해 설명된 것과 동일한 방식으로 일치를 제한합니다. 예를 들어 다음과 같이 메서드 시그니처를 선언할 수 있습니다.
public void doAccessCheck(Object retVal) {...
던진 후 advice은 예외를 던지면서 일치하는 메서드 실행이 종료될 때 실행됩니다. 다음 예제와 같이after-throwing
요소를 사용하여 <aop:aspect>
내부에 선언됩니다.
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut="execution(* com.xyz.dao.*.*(..))"
method="doRecoveryActions"/>
...
</aop:aspect>
@AspectJ 스타일에서처럼 어드바이스 본문 내에서 던져진 예외를 얻을 수 있습니다. 이렇게 하려면 다음 예제와 같이 throwing
속성(attribute)을 사용하여 예외를 전달해야 하는 매개변수의 이름을 지정합니다.
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut="execution(* com.xyz.dao.*.*(..))"
throwing="dataAccessEx"
method="doRecoveryActions"/>
...
</aop:aspect>
doRecoveryActions
메소드는 dataAccessEx
라는 매개변수를 선언해야 합니다. 이 매개변수의 type은 @AfterThrowing
에 대해 설명한 것과 동일한 방식으로 일치를 제한합니다. 예를 들어 메소드 시그니처는 다음과 같이 선언될 수 있습니다.
public void doRecoveryActions(DataAccessException dataAccessEx) {...
after (finally) 어드바이스는 일치하는 메소드 실행이 어떻게 종료되는지에 관계없이 실행됩니다. 다음 예제와 같이 after
요소를 사용하여 이를 선언할 수 있습니다.
<aop:aspect id="afterFinallyExample" ref="aBean">
<aop:after
pointcut="execution(* com.xyz.dao.*.*(..))"
method="doReleaseLock"/>
...
</aop:aspect>
마지막 종류의 advice은 around adivce 것입니다. Around 어드바이스는 일치하는 메서드 실행 "주변"으로 실행합니다. 메서드 실행 전후에 작업을 수행하고 메서드가 실제로 실행되는 시기, 방법, 심지어 실행 여부를 결정할 수 있는 기회가 있습니다. 스레드로부터 안전한 방식으로 메서드 실행 전후에 상태를 공유해야 하는 경우(예: 타이머 시작 및 중지) Around 어드바이스가 자주 사용됩니다.
[Tip]
항상 귀하의 요구 사항을 충족하는 가장 강력한 형태의 advice을 사용하십시오.예를 들어, 사전 조언이 귀하의 요구 사항에 충분하다면 주변 조언을 사용하지 마십시오.
aop:around
요소를 사용하여 around 어드바이스를 선언할 수 있습니다. advice 메소드는 Object
를 반환 type으로 선언해야 하며 메소드의 첫 번째 매개변수는 ProceedingJoinPoint
type이어야 합니다. advice 메소드의 본문 내에서 기본 메소드를 실행하려면 ProceedingJoinPoint
에서 proceed()
를 호출해야 합니다. 인수 없이 proceed()
를 호출하면 호출 시 호출자의 원래 인수가 기본 메서드에 제공됩니다. 고급 사용 사례의 경우 인수 배열(Object[]
)을 허용하는 process()
메서드의 오버로드된 변형이 있습니다. 배열의 값은 호출될 때 기본 메서드에 대한 인수로 사용됩니다. Object[]
를 사용하여 호출 proceed
에 대한 참고사항은 Around Advice
를 참조하세요.
다음 예는 XML에서 어드바이스를 선언하는 방법을 보여줍니다.
<aop:aspect id="aroundExample" ref="aBean">
<aop:around
pointcut="execution(* com.xyz.service.*.*(..))"
method="doBasicProfiling"/>
...
</aop:aspect>
doBasicProfiling
어드바이스의 구현은 다음 예제에서 볼 수 있듯이 @AspectJ 예제(물론 주석 제외)와 정확히 동일할 수 있습니다.
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
스키마 기반 선언 스타일은 @AspectJ 지원에 대해 설명된 것과 동일한 방식으로 완전한 type의 advice을 지원합니다. 즉, advice 메소드 매개변수에 대해 이름별로 포인트컷 매개변수를 일치시키는 것입니다. 자세한 내용은 advice 매개변수를 참조하세요. advice 메소드에 대한 인수 이름을 명시적으로 지정하려면(앞에서 설명한 탐지 전략에 의존하지 않고) argNames
속성(attribute)과 동일한 방식으로 처리되는 advice 어노테이션의 arg-names
속성(attribyte)을 사용하여 그렇게 할 수 있습니다. (인수 이름 결정에 설명된 대로) 다음 예에서는 XML에서 인수 이름을 지정하는 방법을 보여줍니다.
<aop:before
pointcut="com.xyz.Pointcuts.publicMethod() and @annotation(auditable)" <!-- (1) -->
method="audit"
arg-names="auditable" />
(1) 포인트컷 표현식 결합에 정의된 pointcut이라는 publicMethod
를 참조합니다.
arg-names
속성은 쉼표로 구분된 매개변수 이름 목록을 허용합니다.
XSD 기반 접근 방식에 대해 약간 더 관련된 다음 예는 여러 강력한 type의 매개 변수와 함께 사용되는 몇 가지 주변 advice을 보여줍니다.
package com.xyz.service;
public interface PersonService {
Person getPerson(String personName, int age);
}
public class DefaultPersonService implements PersonService {
public Person getPerson(String name, int age) {
return new Person(name, age);
}
}
다음은 aspect이다. profile(..)
메소드는 강력한 type의 매개변수를 허용하며, 그 중 첫 번째 매개변수는 메소드 호출을 proceed하는 데 사용되는 조인 포인트라는 사실에 주목하세요. 이 매개변수의 존재는 다음 예에서 볼 수 있듯이 profile(..)
이 around
어드바이스로 사용됨을 나타냅니다.
package com.xyz;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
public class SimpleProfiler {
public Object profile(ProceedingJoinPoint call, String name, int age) throws Throwable {
StopWatch clock = new StopWatch("Profiling for '" + name + "' and '" + age + "'");
try {
clock.start(call.toShortString());
return call.proceed();
} finally {
clock.stop();
System.out.println(clock.prettyPrint());
}
}
}
마지막으로 다음 예제 XML 구성은 특정 조인 포인트에 대한 이전 advice의 실행에 영향을 미칩니다.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- this is the object that will be proxied by Spring's AOP infrastructure -->
<bean id="personService" class="com.xyz.service.DefaultPersonService"/>
<!-- this is the actual advice itself -->
<bean id="profiler" class="com.xyz.SimpleProfiler"/>
<aop:config>
<aop:aspect ref="profiler">
<aop:pointcut id="theExecutionOfSomePersonServiceMethod"
expression="execution(* com.xyz.service.PersonService.getPerson(String,int))
and args(name, age)"/>
<aop:around pointcut-ref="theExecutionOfSomePersonServiceMethod"
method="profile"/>
</aop:aspect>
</aop:config>
</beans>
다음 드라이버 스크립트를 고려하십시오.
public class Boot {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
PersonService person = ctx.getBean(PersonService.class);
person.getPerson("Pengo", 12);
}
}
이러한 Boot
클래스를 사용하면 표준 출력에서 다음과 유사한 출력을 얻을 수 있습니다.
StopWatch 'Profiling for 'Pengo' and '12': running time (millis) = 0
-----------------------------------------
ms % Task name
-----------------------------------------
00000 ? execution(getFoo)
여러 개의 advice이 동일한 조인 포인트(실행 방법)에서 실행되어야 하는 경우 순서 규칙은 advice 순서에 설명된 대로입니다. aspect 사이의 우선순위는 <aop:aspect>
요소의 order
속성을 통해 결정되거나 aspect을 뒷받침하는 빈에 @Order
annotation을 추가하거나 빈이 Ordered
인터페이스를 구현하도록 함으로써 결정됩니다.
[Note]
동일한@Aspect
클래스에 정의된 어드바이스 메소드에 대한 우선순위 규칙과 달리, 동일한<aop:aspect>
요소에 정의된 두 개의 어드바이스가 모두 동일한 조인 포인트에서 실행되어야 하는 경우 우선순위는 다음 순서에 따라 결정됩니다. 조언 요소는 둘러싸는<aop:aspect>
요소 내에서 가장 높은 우선순위부터 가장 낮은 우선순위로 선언됩니다.예를 들어, 동일한 조인 포인트에 적용되는 동일한
<aop:aspect>
요소에 정의된around
어드바이스와before
어드바이스가 주어지면around
어드바이스가before
어드바이스보다 높은 우선순위를 갖도록 보장하기 위해<aop:around>
요소<aop:before>
요소 앞에 선언해야 합니다.경험상, 동일한 조인 포인트에 적용되는 동일한
<aop:aspect>
요소에 여러 개의 어드바이스가 정의되어 있는 경우 각 조인 포인트당 하나의 어드바이스 메서드로 이러한 어드바이스 메서드를 축소하는 것을 고려하세요.<aop:aspect>
요소를 사용하거나 advice 조각을 aspect 수준에서 order할 수 있는 별도의<aop:aspect>
요소로 리팩토링하세요.
Introduction(AspectJ에서는 유형 간 선언으로 알려짐)를 통해 aspect은 어드바이스된 객체가 주어진 인터페이스를 구현하고 해당 객체를 대신하여 해당 인터페이스의 구현을 제공한다는 것을 선언할 수 있습니다.
aop:aspect
내에서 aop:declare-parents
요소를 사용하여 소개를 만들 수 있습니다. aop:declare-parents
요소를 사용하여 일치하는 유형에 새로운 상위(따라서 이름)가 있음을 선언할 수 있습니다. 예를 들어, UsageTracked
라는 인터페이스와 DefaultUsageTracked
라는 인터페이스의 구현이 주어지면 다음 aspect
은 서비스 인터페이스의 모든 구현자가 UsageTracked
인터페이스도 구현함을 선언합니다. (예를 들어 JMX를 통해 통계를 노출하기 위해.)
<aop:aspect id="usageTrackerAspect" ref="usageTracking">
<aop:declare-parents
types-matching="com.xyz.service.*+"
implement-interface="com.xyz.service.tracking.UsageTracked"
default-impl="com.xyz.service.tracking.DefaultUsageTracked"/>
<aop:before
pointcut="execution(* com.xyz..service.*.*(..))
and this(usageTracked)"
method="recordUsage"/>
</aop:aspect>
UsageTracking 빈
을 지원하는 클래스에는 다음 메소드가 포함됩니다.
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}
구현될 인터페이스는 implement-interface
속성에 의해 결정됩니다. types-matching
속성의 값은 AspectJ type 패턴입니다. 일치하는 유형의 모든 Bean은 UsageTracked
인터페이스를 구현합니다. 이전 예제의 before advice에서 서비스 Bean은 UsageTracked
인터페이스의 구현으로 직접 사용될 수 있습니다. 프로그래밍 방식으로 Bean에 액세스하려면 다음을 작성할 수 있습니다.
UsageTracked usageTracked = context.getBean("myService", UsageTracked.class);
스키마 정의 aspect에 대해 지원되는 유일한 인스턴스화 모델은 싱글톤 모델입니다. 다른 인스턴스화 모델은 향후 릴리스에서 지원될 수 있습니다.
"advisors" 개념은 Spring에 정의된 AOP 지원에서 비롯되었으며 AspectJ에는 직접적으로 동등한 것이 없습니다. 조언자(advisor)는 단일 advice을 포함하는 작은 독립형 aspect과 같습니다. advice 자체는 빈으로 표현되며 Spring의 advice type에 설명된 advice 인터페이스 중 하나를 구현해야 합니다. Advisor는 AspectJ 포인트컷 표현식을 활용할 수 있습니다.
Spring은 <aop:advisor>
요소로 Advisor 개념을 지원합니다. Spring에서 자체 네임스페이스를 지원하는 트랜잭션 조언과 함께 사용되는 것을 가장 일반적으로 볼 수 있습니다. 다음 예에서는 권고자를 보여줍니다.
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.service.*.*(..))"/>
<aop:advisor
pointcut-ref="businessService"
advice-ref="tx-advice" />
</aop:config>
<tx:advice id="tx-advice">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
이전 예제에서 사용된 pointcut-ref
속성(attribute)뿐만 아니라 pointcut
속성(attribute)을 사용하여 포인트컷 표현식 인라인을 정의할 수도 있습니다.
adivce이 순서에 참여할 수 있도록 advisor의 우선순위를 정의하려면 order
속성을 사용하여 advisor의 Ordered
값을 정의하십시오.
이 섹션에서는 AOP 예제의 동시 잠금 실패 재시도 예제가 스키마 지원으로 다시 작성되었을 때 어떻게 보이는지 보여줍니다.
동시성 문제(예: 교착 상태 패자)로 인해 비즈니스 서비스 실행이 실패하는 경우가 있습니다. 작업을 다시 시도하면 다음 시도에서 성공할 가능성이 높습니다. 이러한 조건(충돌 해결을 위해 사용자에게 돌아갈 필요가 없는 멱등성 작업)에서 재시도하는 것이 적절한 비즈니스 서비스의 경우 클라이언트가 PessimisticLockingFailureException
을 보지 않도록 작업을 투명하게 재시도하려고 합니다. 이는 서비스 계층의 여러 서비스를 명확하게 구분하는 요구 사항이므로 aspect을 통해 구현하는 데 이상적입니다.
작업을 다시 시도하고 싶기 때문에 proceed
을 여러 번 호출할 수 있도록 around
어드바이스를 사용해야 합니다. 다음 목록은 기본 aspect 구현(스키마 지원을 사용하는 일반 Java 클래스)을 보여줍니다.
public class ConcurrentOperationExecutor implements Ordered {
private static final int DEFAULT_MAX_RETRIES = 2;
private int maxRetries = DEFAULT_MAX_RETRIES;
private int order = 1;
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
int numAttempts = 0;
PessimisticLockingFailureException lockFailureException;
do {
numAttempts++;
try {
return pjp.proceed();
}
catch(PessimisticLockingFailureException ex) {
lockFailureException = ex;
}
} while(numAttempts <= this.maxRetries);
throw lockFailureException;
}
}
애스펙트는 Ordered
인터페이스를 구현하므로 트랜잭션 advice보다 애스펙트의 우선순위를 더 높게 설정할 수 있습니다(재시도할 때마다 새로운 트랜잭션을 원함). maxRetries
및 order
속성(properties)은 모두 Spring에 의해 구성됩니다. 주요 작업은 around advice 메소드의 doConcurrentOperation
에서 발생합니다. 우리는 proceed하려고 노력합니다. PessimisticLockingFailureException
으로 인해 실패하는 경우 재시도 횟수를 모두 소진하지 않는 한 다시 시도합니다.
[Note]
이 클래스는 @AspectJ 예제에 사용된 클래스와 동일하지만 어노테이션이 제거되었습니다.
해당 Spring 구성은 다음과 같습니다.
<aop:config>
<aop:aspect id="concurrentOperationRetry" ref="concurrentOperationExecutor">
<aop:pointcut id="idempotentOperation"
expression="execution(* com.xyz.service.*.*(..))"/>
<aop:around
pointcut-ref="idempotentOperation"
method="doConcurrentOperation"/>
</aop:aspect>
</aop:config>
<bean id="concurrentOperationExecutor"
class="com.xyz.service.impl.ConcurrentOperationExecutor">
<property name="maxRetries" value="3"/>
<property name="order" value="100"/>
</bean>
당분간은 모든 비즈니스 서비스가 멱등적이라고 가정합니다. 그렇지 않은 경우 다음 예와 같이 Idempotent
어노테이션을 도입하고 해당 어노테이션을 사용하여 서비스 작업 구현에 어노테이션을 추가하여 실제로 멱등성 작업만 재시도하도록 aspect을 개선할 수 있습니다.
@Retention(RetentionPolicy.RUNTIME)
// marker annotation
public @interface Idempotent {
}
멱등성 작업만 재시도하는 aspect 변경에는 다음과 같이 @Idempotent
작업만 일치하도록 포인트컷 표현식을 정제하는 작업이 포함됩니다.
<aop:pointcut id="idempotentOperation"
expression="execution(* com.xyz.service.*.*(..)) and
@annotation(com.xyz.service.Idempotent)"/>