防止重复提交

常规版

此版本解决了 HashMap 无限增长的问题,它使用数组加下标计数器(reqCacheCounter)的方式,实现了固定数组的循环存储。

当数组存储到最后一位时,将数组的存储下标设置 0,再从头开始存储数据,实现代码如下:

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.Arrays;
 
@RequestMapping("/user")
@RestController
public class UserController {
 
    private static String[] reqCache = new String[100]; // 请求 ID 存储集合
    private static Integer reqCacheCounter = 0; // 请求计数器(指示 ID 存储的位置)
 
    @RequestMapping("/add")
    public String addUser(String id) {
        // 非空判断(忽略)...
        synchronized (this.getClass()) {
            // 重复请求判断
            if (Arrays.asList(reqCache).contains(id)) {
                // 重复请求
                System.out.println("请勿重复提交!!!" + id);
                return "执行失败";
            }
            // 记录请求 ID
            if (reqCacheCounter >= reqCache.length) reqCacheCounter = 0; // 重置计数器
            reqCache[reqCacheCounter] = id; // 将 ID 保存到缓存
            reqCacheCounter++; // 下标往后移一位
        }
        // 业务代码...
        System.out.println("添加用户ID:" + id);
        return "执行成功!";
    }
}

扩展版——双重检测锁(DCL)

上一种实现方法将判断和添加业务,都放入 synchronized 中进行加锁操作,这样显然性能不是很高,于是我们可以使用单例中著名的 DCL(Double Checked Locking,双重检测锁)来优化代码的执行效率,实现代码如下:

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.Arrays;
 
@RequestMapping("/user")
@RestController
public class UserController {
 
    private static String[] reqCache = new String[100]; // 请求 ID 存储集合
    private static Integer reqCacheCounter = 0; // 请求计数器(指示 ID 存储的位置)
 
    @RequestMapping("/add")
    public String addUser(String id) {
        // 非空判断(忽略)...
        synchronized (this.getClass()) {
            // 重复请求判断
            if (Arrays.asList(reqCache).contains(id)) {
                // 重复请求
                System.out.println("请勿重复提交!!!" + id);
                return "执行失败";
            }
            // 记录请求 ID
            if (reqCacheCounter >= reqCache.length) reqCacheCounter = 0; // 重置计数器
            reqCache[reqCacheCounter] = id; // 将 ID 保存到缓存
            reqCacheCounter++; // 下标往后移一位
        }
        // 业务代码...
        System.out.println("添加用户ID:" + id);
        return "执行成功!";
    }
}

一、完善版

Apache 为我们提供了一个 commons-collections 的框架,里面有一个非常好用的数据结构 LRUMap 可以保存指定数量的固定的数据,并且它会按照 LRU 算法,帮你清除最不常用的数据。

LRU 是 Least Recently Used 的缩写,即最近最少使用,是一种常用的数据淘汰算法,选择最近最久未使用的数据予以淘汰。

 <!-- 集合工具类 apache commons collections -->
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-collections4 -->
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-collections4</artifactId>
  <version>4.4</version>
</dependency>

实现代码:

import org.apache.commons.collections4.map.LRUMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RequestMapping("/user")
@RestController
public class UserController {
 
    // 最大容量 100 个,根据 LRU 算法淘汰数据的 Map 集合
    private LRUMap<String, Integer> reqCache = new LRUMap<>(100);
 
    @RequestMapping("/add")
    public String addUser(String id) {
        // 非空判断(忽略)...
        synchronized (this.getClass()) {
            // 重复请求判断
            if (reqCache.containsKey(id)) {
                // 重复请求
                System.out.println("请勿重复提交!!!" + id);
                return "执行失败";
            }
            // 存储请求 ID
            reqCache.put(id, 1);
        }
        // 业务代码...
        System.out.println("添加用户ID:" + id);
        return "执行成功!";
    }
}

2.最终版——封装

以上都是方法级别的实现方案,然而在实际的业务中,我们可能有很多的方法都需要防重,那么接下来我们就来封装一个公共的方法,以供所有类使用:

import org.apache.commons.collections4.map.LRUMap;
 
/**
 * 幂等性判断
 */
public class IdempotentUtils {
 
    // 根据 LRU(Least Recently Used,最近最少使用)算法淘汰数据的 Map 集合,最大容量 100 个
    private static LRUMap<String, Integer> reqCache = new LRUMap<>(100);
 
    /**
     * 幂等性判断
     * @return
     */
    public static boolean judge(String id, Object lockClass) {
        synchronized (lockClass) {
            // 重复请求判断
            if (reqCache.containsKey(id)) {
                // 重复请求
                System.out.println("请勿重复提交!!!" + id);
                return false;
            }
            // 非重复请求,存储请求 ID
            reqCache.put(id, 1);
        }
        return true;
    }
}

调用代码如下:

import com.example.idempote.util.IdempotentUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RequestMapping("/user")
@RestController
public class UserController4 {
    @RequestMapping("/add")
    public String addUser(String id) {
        // 非空判断(忽略)...
        // -------------- 幂等性调用(开始) --------------
        if (!IdempotentUtils.judge(id, this.getClass())) {
            return "执行失败";
        }
        // -------------- 幂等性调用(结束) --------------
        // 业务代码...
        System.out.println("添加用户ID:" + id);
        return "执行成功!";
    }
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值