springboot封装redission的分布式锁逻辑为注解

场景概述

使用分布式锁的时候,每次都需要使用try catch处理方法中的逻辑。考虑是否可以这块逻辑抽离出来。

实现

        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.12.0</version>
        </dependency>

        <!-- aop切面 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

在自定义的注解中添加属性来设置锁的等待时间、租赁时间和时间单位


/**
 * @author czf
 * @Description: redisson分布式锁注解
 * @date 2023-07-10 20:53
 */
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {
    String value() default ""; // 锁的名称或标识符
    long waitTime() default 0; // 等待获取锁的时间
    long leaseTime() default -1; // 锁的租赁时间,-1表示无限期
    TimeUnit timeUnit() default TimeUnit.SECONDS; // 时间单位 秒
}

在切面类中获取注解的属性值,并将其传递给Redisson锁对象

package com.example.mybatisplus.aop;

import com.example.mybatisplus.annotation.DistributedLock;
import lombok.extern.slf4j.Slf4j;
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.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @author czf
 * @Description: 分布式锁切面
 * @date 2023-07-10 20:55
 */
@Slf4j
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 1 ) // 设置切面的优先级为最高
public class DistributedLockAspect {

    private final RedissonClient redissonClient;

    /**
     * 用于SpEL表达式解析
     */
    private final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();


    /**
     * 用于获取方法参数定义名字
     */
    private final DefaultParameterNameDiscoverer defaultParameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    @Autowired
    public DistributedLockAspect(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    @Around("@annotation(distributedLock)")
    public Object applyLock(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
        String SpeL = distributedLock.value();
        String keyBySpeL = getKeyBySpeL(SpeL, joinPoint);
        log.info("keyBySpeL:"+keyBySpeL);
        RLock lock = redissonClient.getLock(keyBySpeL);
        long waitTime = distributedLock.waitTime();
        long leaseTime = distributedLock.leaseTime();
        TimeUnit timeUnit = distributedLock.timeUnit();

        boolean isLocked = false;
        try {
            isLocked = lock.tryLock(waitTime, leaseTime, timeUnit);
            if (isLocked) {
                log.info("线程:"+Thread.currentThread().getName()+"获取到锁");
                return joinPoint.proceed();
            } else {
                log.info("未获取到锁对象!");
            }
        } catch (Exception e){
            log.info("异常信息:"+ e.getMessage());
        }finally {
            // 是否是当前执行线程的锁
            if (isLocked && lock.isHeldByCurrentThread()) {
                // 释放锁
                lock.unlock();
                log.info("线程:"+Thread.currentThread().getName()+"已经释放锁");
            }
        }
        return null;
    }

    /**
     * 获取缓存的value
     * value 定义在注解上,支持SPEL表达式
     *
     * @return String
     */
    public String getKeyBySpeL(String spel, ProceedingJoinPoint proceedingJoinPoint) {
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        String[] paramNames = defaultParameterNameDiscoverer.getParameterNames(methodSignature.getMethod());
        EvaluationContext context = new StandardEvaluationContext();
        Object[] args = proceedingJoinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            context.setVariable(paramNames[i], args[i]);
        }
        return String.valueOf(spelExpressionParser.parseExpression(spel).getValue(context));
    }
}


在Spring Boot应用程序中启用AOP,并在需要加锁的方法上使用自定义的注解

@Service
public class MyService {

    @DistributedLock(value = "#user.id +':'+ #user.name",leaseTime = 10)
    @Override
    public void testRedissonLock(User user) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("执行业务");
    }
}

在上述示例中,waitTime属性设置为5,表示等待锁的最长时间为5秒,leaseTime属性设置为10,表示锁的租赁时间为10秒,timeUnit属性设置为TimeUnit.SECONDS,表示时间单位为秒。

与事务注解一起使用会存在事务失效问题解决方案

在Spring中,注解和事务注解可以一起使用,但需要注意一些细节,以确保事务的正确运行。
默认情况下,Spring的事务管理器会在方法开始时创建一个事务,并在方法结束时提交或回滚事务。然而,如果在方法内部使用了自定义的注解,例如用于分布式锁的注解,事务管理器可能无法正确地处理这种情况,从而导致事务失效。
为了解决这个问题,可以通过调整切面的优先级来确保分布式锁注解在事务注解之前执行。通过设置较高的优先级,分布式锁注解的切面将在事务注解的切面之前执行,从而确保分布式锁的获取和释放在事务之外进行。
具体实现方式可以使用@Order注解或者实现Ordered接口来指定切面的执行顺序。例如,可以为分布式锁注解的切面设置一个较高的优先级,如@Order(1),而事务注解的切面设置一个较低的优先级,如@Order(2)
这样,分布式锁注解的切面会在事务注解的切面之前执行,确保分布式锁的获取和释放不会干扰事务的正常运行。
需要注意的是,使用分布式锁注解和事务注解一起时,要确保分布式锁的获取和释放的时间不会超过事务的执行时间,以避免锁的过期导致数据不一致的问题。可以根据实际情况调整锁的等待时间、租赁时间和时间单位,以确保锁的有效性和事务的一致性。
注意:设置注解切面为最高级1时会报错。详情参考:https://blog.csdn.net/qq_18300037/article/details/128626005
注解中的value属性采用spel表达式。详情参考:https://blog.csdn.net/weixin_43888891/article/details/127520555
spel

posted @   C紫枫  阅读(414)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
点击右上角即可分享
微信分享提示