【分布式】基于Redisson实现对分布式锁的注解式封装

基于Redisson实现对分布式锁的注解式封装

实现结果

可以基于注解的方式给一个方法体添加分布式锁 并实现自动释放

示例

java 复制代码
@Service
public class OrderService {
    
    @DistributeLock(
        scene = "order",
        keyExpression = "#orderId",
        expireTime = 3000,
        waitTime = 1000
    )
    public void createOrder(String orderId, User user) {
        // 处理订单创建逻辑,自动加锁/解锁
        // 锁键为 "order#123"(假设 orderId=123)
    }
    
    @DistributeLock(
        scene = "stock",
        key = "product_#productId",
        expireTime = 5000
    )
    public void deductStock(Long productId, int quantity) {
        // 锁键为 "stock#product_1001"(假设 productId=1001)
    }
}

实现@DistributeLock注解

配置一些加锁必须的参数说明

java 复制代码
/**
 * 分布式锁注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributeLock {

    /**
     * 锁的场景
     *
     * @return
     */
    public String scene();

    /**
     * 加锁的key,优先取key(),如果没有,则取keyExpression()
     *
     * @return
     */
    public String key() default DistributeLockConstant.NONE_KEY;

    /**
     * SPEL表达式:
     * <pre>
     *     #id
     *     #insertResult.id
     * </pre>
     *
     * @return
     */
    public String keyExpression() default DistributeLockConstant.NONE_KEY;

    /**
     * 超时时间,毫秒
     * 默认情况下不设置超时时间,会自动续期
     *
     * @return
     */
    public int expireTime() default DistributeLockConstant.DEFAULT_EXPIRE_TIME;

    /**
     * 加锁等待时长,毫秒
     * 默认情况下不设置等待时长,会一直等待直到获取到锁
     * @return
     */
    public int waitTime() default DistributeLockConstant.DEFAULT_WAIT_TIME;
}

实现分布式锁切面

加锁逻辑

  1. 反射的方式获取方法体 并从方法体上拿到我们定义的注解和注解里面配置的值
  2. 判断是否设置了key 如果没有 判断是否设置了SqEL表达式 如果都没有 抛出异常
  3. 如果是设置了 SqEL 解析SqEL 拼接分布式锁key 如果设置了key 直接拼接分布式锁key
  4. 如果没有设置等待时间 通过lock()加锁 默认无限等待 如果设置了等待实现 使用tryLock()配置等待时间
  5. 如果设置了超时时间 加锁时添加超时时间 默认单位是毫秒
  6. 执行方法体
  7. 判断锁是否被等钱线程占用 如果是则释放锁 避免将别的线程的锁给释放
java 复制代码
/**
 * 分布式锁切面
 *
 * @author hollis
 */
@Aspect
@Component
@Order(Integer.MIN_VALUE + 1)
public class DistributeLockAspect {

    private RedissonClient redissonClient;

    public DistributeLockAspect(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    private static final Logger LOG = LoggerFactory.getLogger(DistributeLockAspect.class);

    @Around("@annotation(cn.hollis.nft.turbo.lock.DistributeLock)")
    public Object process(ProceedingJoinPoint pjp) throws Exception {
        Object response = null;
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        DistributeLock distributeLock = method.getAnnotation(DistributeLock.class);

        String key = distributeLock.key();
        if (DistributeLockConstant.NONE_KEY.equals(key)) {
            if (DistributeLockConstant.NONE_KEY.equals(distributeLock.keyExpression())) {
                throw new DistributeLockException("no lock key found...");
            }
            /**
             * 例如
             * @DistributeLock(
             *     scene = "order",
             *     keyExpression = "#orderId"
             * )
             * public void processOrder(String orderId, User user) {
             *     // 处理订单逻辑
             * }
             *
             */
            //拿到解析器
            SpelExpressionParser parser = new SpelExpressionParser();
            //解析SqEL
            Expression expression = parser.parseExpression(distributeLock.keyExpression());

            EvaluationContext context = new StandardEvaluationContext();
            /**
             * processOrder("ORD123", user)
             */
            // 获取参数值 ["ORD123", user]
            Object[] args = pjp.getArgs();

            // 获取运行时参数的名称
            // (String orderId, User user)
            StandardReflectionParameterNameDiscoverer discoverer
                    = new StandardReflectionParameterNameDiscoverer();
            String[] parameterNames = discoverer.getParameterNames(method);

            // 将参数绑定到context中
            /**
             * context.setVariable("orderId", "ORD123");
             * context.setVariable("user", user);
             */
            if (parameterNames != null) {
                for (int i = 0; i < parameterNames.length; i++) {
                    context.setVariable(parameterNames[i], args[i]);
                }
            }
            //expression.getValue(context) 解析 #orderId 表达式,获取绑定的参数值 "ORD123"。
            key = String.valueOf(expression.getValue(context));
        }
        String scene = distributeLock.scene();
        //结合 scene = "order",最终锁键为:order#ORD123
        String lockKey = scene + "#" + key;


        int expireTime = distributeLock.expireTime();//配置过期时间
        int waitTime = distributeLock.waitTime();//等待获取锁的时间
        RLock rLock= redissonClient.getLock(lockKey);// 获取Redisson实例
        try {
            boolean lockResult = false;
            //根据不同的锁等待时间 选择不同的枷锁策略
            //如果是默认 无限等待时间 适用于非高并发场景
            if (waitTime == DistributeLockConstant.DEFAULT_WAIT_TIME) {
                //没有设置超时时间
                if (expireTime == DistributeLockConstant.DEFAULT_EXPIRE_TIME) {
                    LOG.info(String.format("lock for key : %s", lockKey));
                    rLock.lock();
                } else {
                    //设置了超时时间 默认单位是毫秒
                    LOG.info(String.format("lock for key : %s , expire : %s", lockKey, expireTime));
                    rLock.lock(expireTime, TimeUnit.MILLISECONDS);
                }
                lockResult = true;
            } else {
                //带超时时间的锁 适用于高并发场景
                //未设置锁的超时时间
                if (expireTime == DistributeLockConstant.DEFAULT_EXPIRE_TIME) {
                    LOG.info(String.format("try lock for key : %s , wait : %s", lockKey, waitTime));
                    lockResult = rLock.tryLock(waitTime, TimeUnit.MILLISECONDS);
                } else {
                    //设置了锁的超时时间
                    LOG.info(String.format("try lock for key : %s , expire : %s , wait : %s", lockKey, expireTime, waitTime));
                    lockResult = rLock.tryLock(waitTime, expireTime, TimeUnit.MILLISECONDS);
                }
            }

            //获取锁失败 抛出异常
            if (!lockResult) {
                LOG.warn(String.format("lock failed for key : %s , expire : %s", lockKey, expireTime));
                throw new DistributeLockException("acquire lock failed... key : " + lockKey);
            }


            LOG.info(String.format("lock success for key : %s , expire : %s", lockKey, expireTime));
            //执行目标方法
            response = pjp.proceed();
        } catch (Throwable e) {
            throw new Exception(e);
        } finally {
            //最后如果锁被当前线程占有 释放当前线程的锁 避免误释放
            if (rLock.isHeldByCurrentThread()) {
                rLock.unlock();
                LOG.info(String.format("unlock for key : %s , expire : %s", lockKey, expireTime));
            }
        }
        return response;
    }
}
相关推荐
xcl09255 小时前
商超智能运营实战:数据驱动提升门店效率与体验指南
java·mfc·宠物
Flynt7 小时前
OpenJDK禁AI代码半年了,现在执行得怎么样?答案是:全靠自觉
java·开源·ai编程
这个DBA有点耶12 小时前
2026年分布式数据库有哪些主流选择?先评估这5个维度再决定
数据库·分布式·dba
古法安卓12 小时前
Android-日志系统源码解析
android·java·android studio
小夏coding14 小时前
从"一把梭"到"精妙拆解" —— 滑动窗口计时框架的设计演进
java·后端
MacroZheng15 小时前
同事问我:"Claude Code经常失忆,不怕它把项目搞炸?",我:"怕,三个Markdown文件给它装个永不丢失的外置大脑!"
java·人工智能·后端
十年Java程序媛16 小时前
深度实战:JDK21 虚拟线程 + HikariCP 生产正确配比、坑点、监控全套
java·spring boot
xiaoqiMikko16 小时前
有人在搜一个不存在的 Tomcat 版本
java·tomcat
用户31268748772019 小时前
ConcurrentHashMap 怎么保证线程安全?从分段锁到 CAS+synchronized
java
vipxieliang20 小时前
ValidX vs Apache Commons Validator:功能与性能对比
java·spring boot