ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
现在已经知道各部分是如何工作的了, 把他们组合在一起做点有意义的事! 业务服务的执行在并发时可能会失败(死锁).如果重试该操作,则很可能在下一轮成功,适用于重试的商业服务(满足幂等操作,无需返回用户进行冲突解决,),我们显示的重试操作,避免客户端看到`PessimisticLockingFailureException`,这是多个切面建议执行在同一个服务上的. 因为要重试方法,只能采用包围建议,如下: ~~~java @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; } } ~~~ 注意,切面实现了`Ordered`接口,我们可以设置优先级高于事务建议(每一次的重试都是一次新的事务).通过spring设置参数`maxRetries`和` order`.主要的操作在包围建议方法`doConcurrentOperation `内.这里是对所有的方法`businessService()`尝试重试逻辑.每次遇到`PessimisticLockingFailureException `异常会重试执行,知道耗光重试次数. 对应的spring配置: ~~~xml <aop:aspectj-autoproxy/> <bean id="concurrentOperationExecutor" class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor"> <property name="maxRetries" value="3"/> <property name="order" value="100"/> </bean> ~~~ 为了明确切面只是重试幂等操作,可以定义注解`Idempotent `: ~~~java @Retention(RetentionPolicy.RUNTIME) public @interface Idempotent { // marker annotation } ~~~ 实现业务方法需要添加注解`@Idempotent`,切面表达式也需要匹配注解 ~~~java @Around("com.xyz.myapp.SystemArchitecture.businessService() && " + "@annotation(com.xyz.myapp.service.Idempotent)") public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable { ... } ~~~ 自己动手写一个例子: 首先要有一个已存在的业务方法作为连接点,这里是简单的打印日志 ~~~java import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @Component public class JoinPoint { Logger log = LoggerFactory.getLogger(this.getClass()); public String say(String name) { log.info("hello:{}",name); return "hello" + name; } } ~~~ 然后,定义切面,指定切点表达式和建议执行的位置,这里切点和建议合并写在一起. 另外一定要注意,要让切面起作用,切面上的三个注解是必须的 ~~~java import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Aspect @Configuration @EnableAspectJAutoProxy public class BeforeExample { Logger log = LoggerFactory.getLogger(this.getClass()); @Before("execution(* com.ixinnuo.financial.knowledge.aop.JoinPoint.say(..))") public void doAccessCheck(JoinPoint jp) { log.info("BeforeExample... "); Object[] args = jp.getArgs(); for (Object object : args) { log.info("参数{}",args); } } } ~~~ 最后,原来调用业务逻辑的地方,不需要任何改动,正常执行即可 ~~~java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller @RequestMapping("run") public class RunController { @Autowired private JoinPoint joinPoint; @GetMapping("runAop") @ResponseBody public String runAop(){ joinPoint.say("hanmeimei"); return "ok"; } } ~~~ 下面是输出的日志信息,对应程序的结构,看出定义的切面已经执行了 ; 注意BeforeExample的代理实例只会创建一次,不会每次调用都创建 ~~~ [2018-07-23 14:15:05.692][INFO][9124-[http-nio-80-exec-1] BeforeExample$$EnhancerBySpringCGLIB$$69278439:20] - BeforeExample... [2018-07-23 14:15:05.693][INFO][9124-[http-nio-80-exec-1] BeforeExample$$EnhancerBySpringCGLIB$$69278439:23] - 参数hanmeimei [2018-07-23 14:15:05.702][INFO][9124-[http-nio-80-exec-1] JoinPoint:13] - hello:hanmeimei ~~~