简单翻译,更多的是记录怎么用
OOP关注的是类,而AOP关注的是切面,它能够跨越多个类实现统一的事物管理功能等。SpringIOC不依赖AOP
这些概念不是Spring独有的,而是已经存在的,Spring也是拿来主义
Aspect:切面,指的是横切多个类的一种模块。在Spring中,切面用的就是普通的类(xml或者带@Aspect注解配置):
Joint point: 连接点,表示要横切的方法。
Advice:建议,一个切面在特定的连接点执行的方法。建议有::等
PointCut:切入点,能匹配上连接点的那些方法,Advice和切点表达式有关,它会在任何匹配的连接点执行。Spring使用AspectJ的切入点表达式来表示连接点
Introduction:引入,声明额外的方法或者属性,比如让bean实现一个接口
Target Object: 指已经被一个或者多个切面建议过的对象
Aop proxy:Aop代理,采用JDK代理或者CGLIB代理
Weaving:织入,织入切面的过程,可以在编译,加载,或者运行时进行织入,对于Spring AOP来说,是在运行时起作用的。
Advice的种类:
Before advice :连接点运行之前运行
After returning advice:连接点正常运行之后进行,比如一个方法正常return并且没有抛出异常
After throwing advice:抛出异常之后运行
After(finally) advice:不论是连接点是正常结束还是抛异常结束都会执行
Aroud advice:在方法调用之前和之后运行,是一个非常强大的advice,能够自定义方法的行为
SpringAOP是纯java实现的,因此不需要特殊的编译过程。SpringAOP不会该变类加载过程,因此适合Servlet容器使用。
SpringAOP当前仅支持方法连接点,对于属性不会拦截。SpringAOP并不是AOP完整的实现,它的主要目的也不是这个,而是能够和IOC一起整合,提供能够解决企业开发的常见问题的能力。
SpringAOP默认使用标准的JDK代理,也就是代理接口的实现类。SpringAOP也会使用CGLIB代理,在类没有实现接口的时候。
@AspectJ指的是一种风格,也就是使用普通类通过注解来声明一个切面。这种@AspectJ风格在AspectJ项目中也已经引入,Spring使用了和它同样的注解方式,因此在编译时需要AspectJ的类库提供切入点的解析和匹配,但这仅在编译时起作用,运行时仍然是纯SpringAOP
可以通过XML配置和注解的方式开启支持,但是都需要依赖Aspect J的aspectjweaver.jar包
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
<aop:aspectj-autoproxy/>
当开启@AspectJ支持后,容器中任何使用@Aspect注解的类都会被Spring识别用来配置AOP,下面是个案例:
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
bean>
上面的配置是注入(或者在下面加入@Component),下面的配置是配置切面
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}
被注解为@Asepct的类是不能够被其他Asepct切入,因为该类已经不能被自动代理了
切入点声明包括两个部分:一个方法签名以及迁入切入点表达式。如果使用注解的方式定义切入点,该切入点方法的返回值必须是void类型:
@Pointcut("execution(* transfer(..))")// the pointcut expression
private void anyOldTransfer() {}// the pointcut signature
这里的切入点表达式大多和AspectJ5一样
execution: 匹配连接点
within: 某个类里面
this: 指定AOP代理类的类型
target:指定目标对象的类型
args: 指定参数的类型
bean:指定特定的bean名称,可以使用通配符(Spring自带的)
@target: 带有指定注解的类型
@args: 指定运行时传的参数带有指定的注解
@within: 匹配使用指定注解的类
@annotation:指定方法所应用的注解
注意:
由于AOP是使用代理实现的方式,因此并不是目标类的所有方法都能拦截。对于JDK代理,只有public接口的方法才能被拦截,对于CGLIB,只有public和protected方法能被拦截
切入点表达式可以用&&,||,!来组合使用
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}
@Pointcut("within(com.xyz.someapp.trading..*)")
private void inTrading() {}
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}
@Aspect
public class SystemArchitecture {
/**
* A join point is in the web layer if the method is defined
* in a type in the com.xyz.someapp.web package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.web..*)")
public void inWebLayer() {}
/**
* A join point is in the service layer if the method is defined
* in a type in the com.xyz.someapp.service package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.service..*)")
public void inServiceLayer() {}
/**
* A join point is in the data access layer if the method is defined
* in a type in the com.xyz.someapp.dao package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.dao..*)")
public void inDataAccessLayer() {}
/**
* A business service is the execution of any method defined on a service
* interface. This definition assumes that interfaces are placed in the
* "service" package, and that implementation types are in sub-packages.
*
* If you group service interfaces by functional area (for example,
* in packages com.xyz.someapp.abc.service and com.xyz.someapp.def.service) then
* the pointcut expression "execution(* com.xyz.someapp..service.*.*(..))"
* could be used instead.
*
* Alternatively, you can write the expression using the 'bean'
* PCD, like so "bean(*Service)". (This assumes that you have
* named your Spring service beans in a consistent fashion.)
*/
@Pointcut("execution(* com.xyz.someapp..service.*.*(..))")
public void businessService() {}
/**
* A data access operation is the execution of any method defined on a
* dao interface. This definition assumes that interfaces are placed in the
* "dao" package, and that implementation types are in sub-packages.
*/
@Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
public void dataAccessOperation() {}
}
使用这些定义的pointcut:
"com.xyz.someapp.SystemArchitecture.businessService()"
advice-ref="tx-advice"/>
"tx-advice">
"*" propagation="REQUIRED"/>
大多数AOP的使用案例中会采用execution来定义pointcut,定义格式:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
除了返回类型,名称和参数之前,其他都是可选的,返回类型的定义比较复杂,比如:
():匹配没有参数
(..):匹配人任意多个参数
(*):只要一个参数,但是匹配任意类型
(*, String):两个参数
案例:
execution(public * *(..))
execution(* set*(..))
execution(* com.xyz.service.AccountService.*(..))
execution(* com.xyz.service.*.*(..))
execution(* com.xyz.service..*.*(..))
within(com.xyz.service.*)
within(com.xyz.service..*)
this(com.xyz.service.AccountService):代理实现了AccountService接口
target(com.xyz.service.AccountService):目标对象实现了AccoutService接口
args(java.io.Serializable):表示传递的参数是Serializable
@target(org.springframework.transaction.annotation.Transactional):目标对象有@Transactional注解
@within(org.springframework.transaction.annotation.Transactional)
@annotation(org.springframework.transaction.annotation.Transactional):方法有@Transactional注解
@args(com.xyz.security.Classified):有一个参数,并且传递的类型有@Classfied注解
bean(tradeService):bean的名字,可以使用通配符
建议的声明适合切入点的声明相关的,下面的例子可以放在一起,不过为了说明,分散在不同的类了
使用@Before注解:
@Aspect
public class BeforeExample {
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
也可以内置切入点表达式:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("execution(* com.xyz.myapp.dao.*.*(..))")
public void doAccessCheck() {
// ...
}
}
正常返回后会执行
@Aspect
public class AfterReturningExample {
@AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
有些时候需要访问实际返回的值,这时候可以绑定相应的值,returning属性的值,必须是advice方法的参数。当方法执行返回后,这个值会传递到advice中。这里面使用Object所谓类型表示匹配所有的范围值,使用其他的类型也可以用来缩小匹配的连接点范围。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal) {
// ...
}
}
@Aspect
public class AfterThrowingExample {
@AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doRecoveryActions() {
// ...
}
}
或者限制更加详细的异常类型,同样的,throwing的值也必须是方法的参数,而且添加throwing也会限制匹配的切入点
@Aspect
public class AfterThrowingExample {
@AfterThrowing(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ...
}
}
在方法执行前和执行后都能够操作,它非常强大,能够决定程序什么时候,什么样的 方式,甚至是运行的逻辑。Around advice方法第一个参数为固定值:ProceedingJoinPoint,可以在方法内调用proceed()继续执行方法,同时还可以对它进行传参,参数是Object[],对应的就是拦截的方法需要传的参数数组
@Aspect
public class AroundExample {
@Around("com.xyz.myapp.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
}
任何一个advice方法都可以添加一个JoinPoint类型的参数,对于Around advice这个参数是必须的,JoinPoint的常见方法:getArgs(),getThis(),getTarget(),getSignature()
除了上面讲到的传参数方式,advice还支持通过args标签的传参
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
// ...
}
上面的args(account,..)有两个作用:1.限制方法的参数,即至少有一个参数,且该参数为Account类(advice方法的参数类型决定)2.让这个Advice能够访问Account类的一个对象。另一种写法:
@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
// ...
}
@annotation的案例:
这里也能访问到使用的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
AuditCode value();
}
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
AuditCode code = auditable.value();
// ...
}
advice的泛型的使用:
假设有个类使用了泛型:
public interface Sample<T> {
void sampleGenericMethod(T param);
void sampleGenericCollectionMethod(Collection param);
}
使用AOP时,可以限制泛型方法的泛型类型,本案例限制为MyType:
注意这里的execution的使用了+号,表示不仅匹配该接口,也匹配该接口的实现类
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
// Advice implementation
}
但是这种方法不适合泛型集合,下面的这种写法就不起作用:
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection param) {
// Advice implementation
}
实际上前面介绍的一系列的切入点表达式都可以和上面的一样绑定参数
确定参数的名称(手动指定或者开启编译选项):
advice中的参数的名字要匹配切入点表达式中定义的参数名字。但是参数的名字通过反射是获取不到的。所以SpringAOP使用了下面的方式却确定参数的名字:
手动指定:使用argNames指定方法的名字
如下:
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
argNames="bean,auditable")
public void audit(Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code and bean
}
如果第一个参数是JoinPoint或者ProceedingJoinPoint或者JoinPoint.StaticPart类型,可以在指定参数中省略.
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
argNames="bean,auditable")
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code, bean, and jp
}
反之,如果参数是上面那几种之一,可以省略argNames
@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
public void audit(JoinPoint jp) {
// ... use jp
}
如果没有手动指定,这时候如果开启了“-g:vars”编译选项,SpringAOP能够查找到参数的名字
如果没有开启该编译选项,Spring会进行推断,如果推断有歧义会抛异常
如果上述策略都失败,会抛参数异常
Advice中继续执行方法:
前面提到过,可以在Advice传参让切入点方法继续执行,和AspectJ不同的是,SpringAOP中传参(Object数组)让方法继续执行,只要参数能够匹配上切入点的方法参数就行,但是对于AspectJ,传参需要和Advice方法的参数一致。为了在两种环境效果一致,advice方法的参数的顺序要和切入点方法的参数一致
@Around("execution(List find*(..)) && " +
"com.xyz.myapp.SystemArchitecture.inDataAccessLayer() && " +
"args(accountHolderNamePattern)")
public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
String accountHolderNamePattern) throws Throwable {
String newPattern = preProcess(accountHolderNamePattern);
return pjp.proceed(new Object[] {newPattern});
}
优先级越高的advice,进入方法的时候会最先执行,退出后会最后执行。正常所有的advice是没有顺序的,在同IOC介绍的一行,在Spring中可以在Aspect类中继承Ordered接口来实现定义顺序,数值越低,优先级越高,或者使用@Order的注解。
能够让那些被adviced的类实现一个接口。引入是通过@DeclareParents注解实现的,能够让匹配的类型增加新的parent。例如假设有个接口UsageTracked,以及一个实现DefaultUsageTracked, 下面的aspect声明让所有的service实现类(即businessService包中的类)同样实现UsageTracked接口:
@Aspect
public class UsageTracking {
//接口类变量,用来指定要继承的接口
@DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
public static UsageTracked mixin;
//this引用了usageTracked的bean的类型
@Before("com.xyz.myapp.SystemArchitecture.businessService() && this(usageTracked)")
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}
}
默认情况下,IOC中只有一个Aspect的实例。Spring支持额外的生命周期:perthis,pertarget
perthis:joinpoint中每个this匹配的对象创建一个实例,当第一次调用service对象的一个方法时,会创建aspect实例。当service对象出了scope后,aspect也会出scope。aspect实例化之前,advice不会起作用。
@Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())")
public class MyAspect {
private int someState;
@Before(com.xyz.myapp.SystemArchitecture.businessService())
public void recordServiceUsage() {
// ...
}
}
pertarget: 每个target匹配的对象创建一个aspect实例
当我们调用某些服务的时候,由于并发可能会导致有些失败的场景,但是下一次访问可能就成功了,这种情况我们要在失败的时候进行retry。
因为我们要retry,所以这里使用Around advice:
@Aspect
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;
}
@Around("com.xyz.myapp.SystemArchitecture.businessService()")
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;
}
}
这里面设置的order比事务管理的order大,这就保证每次retry都是一个新的事务。Spring的配置:
<aop:aspectj-autoproxy/>
<bean id="concurrentOperationExecutor" class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
<property name="maxRetries" value="3"/>
<property name="order" value="100"/>
bean>
为了能够只重试幂等的操作,这里自定义了幂等的注解
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
// marker annotation
}
然后使用该注解作用在service类上。这时候Aspect类可以变为:
@Around("com.xyz.myapp.SystemArchitecture.businessService() && " +
"@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
...
}
### 声明一个切面
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
aop:aspect>
aop:config>
<bean id="aBean" class="...">
...
bean>
当然也可以声明多个
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
aop:config>
或者:
<aop:config>
<aop:pointcut id="businessService"
expression="com.xyz.myapp.SystemArchitecture.businessService()"/>
aop:config>
向advice传参:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.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.myapp.service..(..)) and this(service)"/>
<aop:before pointcut-ref="businessService" method="monitor"/>
...
aop:aspect>
aop:config>
dataAccessOperation是aop:config中配置的切入点,method是advice具体的方法
<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
aop:aspect>
如果是定义内嵌的切入点:
"beforeExample" ref="aBean">
"execution(* com.xyz.myapp.dao.*.*(..))"
method="doAccessCheck"/>
...
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
aop:aspect>
指定返回值,这时候advice方法必须有一个参数名字是retVal。
"afterReturningExample" ref="aBean">
"dataAccessOperation"
returning="retVal"
method="doAccessCheck"/>
...
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut-ref="dataAccessOperation"
method="doRecoveryActions"/>
...
aop:aspect>
指定传递的异常参数(作为advice方法的参数):
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut-ref="dataAccessOperation"
throwing="dataAccessEx"
method="doRecoveryActions"/>
...
aop:aspect>
<aop:aspect id="afterFinallyExample" ref="aBean">
<aop:after
pointcut-ref="dataAccessOperation"
method="doReleaseLock"/>
...
aop:aspect>
同样第一个参数必须是ProceedingJoinPoint类型
<aop:aspect id="aroundExample" ref="aBean">
<aop:around
pointcut-ref="businessService"
method="doBasicProfiling"/>
...
aop:aspect>
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
同样xml也支持类型化(指定参数)的advice.参数名可以用逗号隔开
<aop:before
pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)"
method="audit"
arg-names="auditable"/>
案例:
package x.y.service;
public interface FooService {
Foo getFoo(String fooName, int age);
}
public class DefaultFooService implements FooService {
public Foo getFoo(String name, int age) {
return new Foo(name, age);
}
}
下面的切面方法有多个指定类型的参数
package x.y;
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的配置:
<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 http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<bean id="profiler" class="x.y.SimpleProfiler"/>
<aop:config>
<aop:aspect ref="profiler">
<aop:pointcut id="theExecutionOfSomeFooServiceMethod"
expression="execution(* x.y.service.FooService.getFoo(String,int))
and args(name, age)"/>
<aop:around pointcut-ref="theExecutionOfSomeFooServiceMethod"
method="profile"/>
aop:aspect>
aop:config>
beans>
使用:
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import x.y.service.FooService;
public final class Boot {
public static void main(final String[] args) throws Exception {
BeanFactory ctx = new ClassPathXmlApplicationContext("x/y/plain.xml");
FooService foo = (FooService) ctx.getBean("fooService");
foo.getFoo("Pengo", 12);
}
}
输出:
StopWatch 'Profiling for 'Pengo' and '12'': running time (millis) = 0
-----------------------------------------
ms % Task name
-----------------------------------------
00000 ? execution(getFoo)
和注解的一样,要不实现Ordered的接口或者使用Order的注解
和注解的示例一样
<aop:aspect id="usageTrackerAspect" ref="usageTracking">
<aop:declare-parents
types-matching="com.xzy.myapp.service.*+"
implement-interface="com.xyz.myapp.service.tracking.UsageTracked"
default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/>
<aop:before
pointcut="com.xyz.myapp.SystemArchitecture.businessService()
and this(usageTracked)"
method="recordUsage"/>
aop:aspect>
切面的方法:
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}
xml定义的aspect都是单例的
AspectJ中没有想的概念,这里一个advice用一个类表示,常见的用法:
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.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>
前面注解用到的案例用xml改写:
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;
}
}
<aop:config>
<aop:aspect id="concurrentOperationRetry" ref="concurrentOperationExecutor">
<aop:pointcut id="idempotentOperation"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
<aop:around
pointcut-ref="idempotentOperation"
method="doConcurrentOperation"/>
aop:aspect>
aop:config>
<bean id="concurrentOperationExecutor"
class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
<property name="maxRetries" value="3"/>
<property name="order" value="100"/>
bean>
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
// marker annotation
}
<aop:pointcut id="idempotentOperation"
expression="execution(* com.xyz.myapp.service.*.*(..)) and
@annotation(com.xyz.myapp.service.Idempotent)"/>
<aop:config proxy-target-class="true">
aop:config>
<aop:aspectj-autoproxy proxy-target-class="true"/>