Spring Boot 使用 AOP来防止重复提交

断了今生、忘了曾经 提交于 2020-07-28 20:49:45

思路

  1. 自定义注解 @NoRepeatSubmit 标记所有Controller中的提交请求

  2. 通过AOP 对所有标记了 @NoRepeatSubmit 的方法拦截

  3. 在业务方法执行前,获取当前用户的 token(或者JSessionId)+ 当前请求地址,作为一个唯一 KEY,去获取 Redis 分布式锁(如果此时并发获取,只有一个线程会成功获取锁)

  4. 业务方法执行后,释放锁

关于Redis 分布式锁

  • 不了解的同学戳这里 ==> Redis分布式锁的正确实现方式

  • 使用Redis 是为了在负载均衡部署,如果是单机的部署的项目可以使用一个线程安全的本地Cache 替代 Redis

Code

这里只贴出 AOP 类和测试类,完整代码见 ==> Gitee

 @Aspect
 @Component
 public class RepeatSubmitAspect {
 ​
     private final static Logger LOGGER = LoggerFactory.getLogger(RepeatSubmitAspect.class);
 ​
     @Autowired
     private RedisLock redisLock;
 ​
     @Pointcut("@annotation(noRepeatSubmit)")
     public void pointCut(NoRepeatSubmit noRepeatSubmit) {
     }
 ​
     @Around("pointCut(noRepeatSubmit)")
     public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {
         int lockSeconds = noRepeatSubmit.lockTime();
 ​
         HttpServletRequest request = RequestUtils.getRequest();
         Assert.notNull(request, "request can not null");
 ​
         // 此处可以用token或者JSessionId
         String token = request.getHeader("Authorization");
         String path = request.getServletPath();
         String key = getKey(token, path);
         String clientId = getClientId();
 ​
         boolean isSuccess = redisLock.tryLock(key, clientId, lockSeconds);
 ​
         if (isSuccess) {
             LOGGER.info("tryLock success, key = [{}], clientId = [{}]", key, clientId);
             // 获取锁成功, 执行进程
             Object result;
             try {
                 result = pjp.proceed();
 ​
             } finally {
                 // 解锁
                 redisLock.releaseLock(key, clientId);
                 LOGGER.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId);
 ​
             }
 ​
             return result;
 ​
         } else {
             // 获取锁失败,认为是重复提交的请求
             LOGGER.info("tryLock fail, key = [{}]", key);
             return new ResultBean(ResultBean.FAIL, "重复请求,请稍后再试", null);
         }
 ​
     }
 ​
     private String getKey(String token, String path) {
         return token + path;
     }
 ​
     private String getClientId() {
         return UUID.randomUUID().toString();
     }
 ​
 }

 

多线程测试

测试代码如下,模拟十个请求并发同时提交

 @Component
 public class RunTest implements ApplicationRunner {
 ​
     private static final Logger LOGGER = LoggerFactory.getLogger(RunTest.class);
 ​
     @Autowired
     private RestTemplate restTemplate;
 ​
     @Override
     public void run(ApplicationArguments args) throws Exception {
         System.out.println("执行多线程测试");
         String url="http://localhost:8000/submit";
         CountDownLatch countDownLatch = new CountDownLatch(1);
         ExecutorService executorService = Executors.newFixedThreadPool(10);
 ​
         for(int i=0; i<10; i++){
             String userId = "userId" + i;
             HttpEntity request = buildRequest(userId);
             executorService.submit(() -> {
                 try {
                     countDownLatch.await();
                     System.out.println("Thread:"+Thread.currentThread().getName()+", time:"+System.currentTimeMillis());
                     ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
                     System.out.println("Thread:"+Thread.currentThread().getName() + "," + response.getBody());
 ​
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             });
         }
 ​
         countDownLatch.countDown();
     }
 ​
     private HttpEntity buildRequest(String userId) {
         HttpHeaders headers = new HttpHeaders();
         headers.setContentType(MediaType.APPLICATION_JSON);
         headers.set("Authorization", "yourToken");
         Map<String, Object> div = new HashMap<>();
         div.put("userId", userId);
         return new HttpEntity<>(div, headers);
     }
 ​
 }

 

成功防止重复提交,控制台日志如下,可以看到十个线程的启动时间几乎同时发起,只有一个请求提交成功了

img

结论

大家有什么要说的,欢迎在评论区留言

对了,小编为大家准备了一套2020最新的Java资料,需要点击下方链接获取方式

1、点赞+评论(勾选“同时转发”)

学习java,你掌握这些。二三线也能轻松拿8K以上

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!