Java中如何避免重复提交请求
一、借助本地锁实现
这种方式主要通过自定义注解、springaop、guavacache来生成本地锁,达到防止重复提交的效果。
二、具体实现
1.引入guava依赖
Guava是谷歌开源的Java库,这个库提供用于集合,缓存,支持原语,并发性,常见注解,字符串处理,I/O和验证的实用方法,对JDK工具做了很好扩展。
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>21.0</version>
</dependency>
2.自定义RepeatSubmit注解
编写自定义注解,用于需要控制重复提交的方法上。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RepeatSubmit {
String key() default "";
}
3.自定义注解切面
编写自定义注解的aop拦截器具体实现,读取有RepeatSubmit注解的方法,解析注解中定义的key值在本地缓存中是否存在,若存在则提示重复请求,若为第一次请求则将key存入本地缓存中。
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
/**
* Author:Qinggq
* Date:2023/6/27
* Description:
*/
@Aspect
@Component
public class RepeatSubmitAspect {
private static final Cache<String,Object> CACHES = CacheBuilder.newBuilder()
// 最大缓存 设置为1000个
.maximumSize(1000)
// 设置写缓存后5s过期
.expireAfterWrite(5, TimeUnit.SECONDS)
.build();
@Around("execution(public * *(..)) && @annotation(com.xxx.Annotation.RepeatSubmit))")
public Object interceptor(ProceedingJoinPoint pjp){
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
RepeatSubmit repeatSubmit = method.getAnnotation(RepeatSubmit.class);
String key = getKey(repeatSubmit.key(),pjp.getArgs());
if (StringUtils.isNotBlank(key)){
if (CACHES.getIfPresent(key) != null){
throw new RuntimeException("请勿重复请求");
}
// 如果是第一次请求,就将key 当前对象压入缓存中
CACHES.put(key,key);
}
try {
return pjp.proceed();
} catch (Throwable throwable) {
throw new RuntimeException("服务器异常!");
}
}
/**
* key 的生成策略
* @param keyExpress 表达式
* @param args 参数
* @return 生成的key
*/
private String getKey(String keyExpress, Object[] args) {
for (int i = 0; i < args.length; i++) {
keyExpress = keyExpress.replace("arg[" + i +"]",args[i].toString());
}
return keyExpress;
}
}
4.控制层实现
在需要限制重复提交的方法上加入@RepeatSubmit注解,其中key值为自定义的存入缓存中的key。
@GetMapping("/query")
//@RepeatSubmit(key = "query:arg[0]") // 参数可以动态获取作为key
@RepeatSubmit(key = "产品数据信息实时同步查询") //也可写死查询的key
public ResultModel query(@RequestParam String token){
try{
// 模拟请求执行时间2s
Thread.sleep(2000);
}catch{
e.printStackTrace();
}
return ResultModel.success();
}
5.效果展示
启动应用,访问上面的/query请求查看效果。
正常访问一下,结果如下:
接下来,在正常访问过程中,重复点击提交,可以看到已达到限制效果。