javaaop被重复调⽤_防⽌重复提交解决⽅案-(基于JAVA注解
+AOP切⾯)
1、前⾔
近期在构建项⽬脚⼿架时,关于接⼝幂等性问题,考虑做成独⽴模块⼯具放进脚⼿架中进⾏通⽤。
如何保证接⼝幂等性,换句话说就是如何防⽌接⼝重复提交。通常,前后端都需要考虑如何实现相关控制。
前端常⽤的解决⽅案是“表单提交完成,按钮置灰、按钮不可⽤或者关闭相关页⾯”。
常见的后端解决⽅案有“基于JAVA注解+AOP切⾯实现防⽌重复提交“。
2、⽅案
基于JAVA注解+AOP切⾯⽅式实现防⽌重复提交,⼀般需要⾃定义JAVA注解,采⽤AOP切⾯解析注解,实现接⼝⾸次请求提交时,将接⼝请求标记(由接⼝签名、请求token、请求客户端ip等组成)存储⾄redis,并设置超时时间T(T时间之后redis清除接⼝请求标记),接⼝每次请求都先检查redis中接⼝标记,若存在接⼝请求标记,则判定为接⼝重复提交,进⾏拦截返回处理。
3、实现
本次采⽤的基础框架为SpringBoot,涉及的组件模块有AOP、WEB、Redis、Lombok、Fastjson。详细代码与配置如下⽂。
pom依赖
1.8
org.springframework.boot
spring-boot-starter-aop
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.boot
spring-boot-starter-test
test
org.projectlombok
lombok
com.alibaba
fastjson
1.2.28
配置⽂件
server.port=8888
# Redis数据库索引(默认为0)
# Redis服务器地址
# Redis服务器连接端⼝
# Redis服务器连接密码(默认为空)
# 连接池最⼤连接数(使⽤负值表⽰没有限制)
# 连接池最⼤阻塞等待时间(使⽤负值表⽰没有限制)
# 连接池中的最⼤空闲连接
# 连接池中的最⼩空闲连接
# 连接超时时间(毫秒)
⾃定义注解
/***@author:Gavin
*@see:防⽌重复操作注解*/@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documentedpublic @interfacePreventDuplication {/*** 防重复操作限时标记数值(存储redis限时标记数值)*/String value()default "value";/*** 防重复操作过期时间(借助redis实现限时控制)*/
long expireSeconds() default 10;
}
⾃定义切⾯(解析注解)
切⾯⽤于处理防重复提交注解,通过redis中接⼝请求限时标记控制接⼝的提交请求。
/***@author:Gavin
*@see:防⽌重复操作切⾯(处理切⾯注解)*/@Aspect
@Componentpublic classPreventDuplicationAspect {
@AutowiredprivateRedisTemplate redisTemplate;/*** 定义切点
*/@Pointcut("@ample.idempotent.idempotent.annotation.PreventDuplication)")public voidpreventDuplication() {
}/*** 环绕通知 (可以控制⽬标⽅法前中后期执⾏操作,⽬标⽅法执⾏前后分别执⾏⼀些代码)
*
*@paramjoinPoint
*@return
*/@Around("preventDuplication()")public Object before(ProceedingJoinPoint joinPoint) throwsException {
ServletRequestAttributes attributes=(ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
HttpServletRequest Request();
Method method =((MethodSignature) Signature()).getMethod();//获取防重复提交注解
PreventDuplication annotation = Annotation(PreventDuplication.class);//获取token以及⽅法标记,⽣成redisKey和redisValue
String token =Header(IdempotentConstant.TOKEN);
String redisKey=IdempotentConstant.PREVENT_DUPLICATION_PREFIX
.concat(token)
.concat(getMethodSign(method, Args()));
String redisValue= at(annotation.value()).concat("submit duplication");if (!redisTemplate.hasKey(redisKey)) {//设置防重复操作限时标记(前置通知)
redisTemplate.opsForValue()
.set(redisKey, redisValue, pireSeconds(), TimeUnit.SECONDS);try{//正常执⾏⽅法并返回//ProceedingJoinPoint类型参数可以决定是否执⾏⽬标⽅法,且环绕通知必须要有返回值,返回值即为⽬标⽅法的返回值
returnjoinPoint.proceed();
}catch(Throwable throwable) {//确保⽅法执⾏异常实时释放限时标记(异常后置通知)
redisTemplate.delete(redisKey);throw newRuntimeException(throwable);
}
}else{throw new RuntimeException("请勿重复提交");
}
}/*** ⽣成⽅法标记:采⽤数字签名算法SHA1对⽅法签名字符串加签
*
*@parammethod
*@paramargs
*@return
*/
privateString getMethodSign(Method method, args) {
StringBuilder sb= String());for(Object arg : args) {
sb.append(toString(arg));
}returnDigestUtils.String());
}privateString toString(Object arg) {if(Objects.isNull(arg)) {return "null";
}if (arg instanceofNumber) {String();
}JSONString(arg);
}
}
public interfaceIdempotentConstant {
String TOKEN= "token";
String PREVENT_DUPLICATION_PREFIX= "PREVENT_DUPLICATION_PREFIX:";
}
controller实现(使⽤注解)
@Slf4j
spring aop应用场景@RestController
@RequestMapping("/web")public classIdempotentController {
@PostMapping("/sayNoDuplication")
@PreventDuplication(expireSeconds= 8)public String sayNoDuplication(@RequestParam("requestNum") String requestNum) {
log.info("sayNoDuplicatin requestNum:{}", requestNum);return "sayNoDuplicatin".concat(requestNum);
}
}
4、测试
正常请求(⾸次)
⾸次请求,接⼝正常返回处理结果。
限定时间内重复请求(上⽂设置8s)
在限定时间内重复请求,AOP切⾯拦截处理抛出异常,终⽌接⼝处理逻辑,异常返回。
控制台报错:
5、源代码
本⽂代码已经上传托管⾄GitHub以及Gitee,有需要的读者请⾃⾏下载。
写在后⾯
本⽂重点在于讲解如何采⽤基于JAVA注解+AOP切⾯快速实现防重复提交功能,该⽅案实现可以完全胜任⾮⾼并发场景下实施应⽤。但是在⾼并发场景下仍然有不⾜之处,存在线程安全问题(可以采⽤Jemeter复现问题)。那么,如何实现⽀持⾼并发场景防重复提交功能?请读者查看我的博⽂《基于Redis实现分布式锁》,这篇博客对本⽂基于JAVA注解+AOP切⾯实现进⾏了优化改造,以便应⽤于⾼并发场景。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。