使用redis+切面完成分布式锁

近日在项目上,需要用到分布式锁,那肯定redis实现了,后来一想干脆写个切面吧,后期方便使用,于是查看了很多文章,然后照猫画虎的实现了,废话不多说,直接上代码!

maven使用包

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
        </dependency>

自定义注解


import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.Order;

/**
 * redis锁注解
 *
 * @author ssh
 * @date 2022/11/01
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
@Order(value = 10)
public @interface RedisLock {
    // 锁前缀
    String lockPrefix() default "";

    // 方法参数名(用于取参数名的值与锁前缀拼接成锁名),尽量不要用对象map等,对象会toString后与锁前缀拼接
    String lockParameter() default "";

    // 尝试加锁,最多等待时间(毫秒)
    long lockWait() default 50L;

    // 自动解锁时间 (毫秒)
    long autoUnlockTime() default 2000L;

    // 重试次数
    int retryNum() default 3;

    // 重试等待时间 (毫秒)
    long retryWait() default 50L;

}

切面实现


import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

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.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.util.Assert;


import lombok.extern.slf4j.Slf4j;

import javax.annotation.Resource;

/**
 * Description: 分布式锁
 * <p>
 * 先获取锁, 获取不到则继续等待(指定时间), 失败次数(指定)次后跳出, 消费降级(抛出,系统繁忙稍后再试) 如果没有重试次数,方法返回null 记得捕获NP 当重试次数有, 但是重试间隔时间没写, 默认200ms 间隔
 * </p>
 *
 * @author ssh
 */
@Aspect
@Slf4j
@Order(10)
public class RedisLockAspect {

    private SpelExpressionParser spelParser = new SpelExpressionParser();

    private static final String LOCK_NAME = "lockName";
    private static final String lOCK_WAIT = "lockWait";
    private static final String AUTO_UNLOCK_TIME = "autoUnlockTime";
    private static final String RETRY_NUM = "retryNum";
    private static final String RETRY_WAIT = "retryWait";

    /**
     * redis工具类
     */
    @Resource
    private RedissonClient redissonClient;


    @Around("@annotation(com.biyao.bim.console.redisLock.RedisLock) && @annotation(redisLock)")
    public Object lockAroundAction(ProceedingJoinPoint proceeding, RedisLock redisLock) throws Throwable {

        // 获取注解中的参数
        Map<String, Object> annotationArgs = this.getAnnotationArgs(proceeding, redisLock);
        String lockName = (String) annotationArgs.get(LOCK_NAME);
        Assert.notNull(lockName, "分布式,锁名不能为空");
        int retryNum = (int) annotationArgs.get(RETRY_NUM);
        long retryWait = (long) annotationArgs.get(RETRY_WAIT);
        long lockWait = (long) annotationArgs.get(lOCK_WAIT);
        long autoUnlockTime = (long) annotationArgs.get(AUTO_UNLOCK_TIME);

        // 获取锁
        RLock lock = redissonClient.getLock(lockName);
        try {
            boolean res = lock.tryLock(lockWait, autoUnlockTime, TimeUnit.SECONDS);
            if (res) {
                // 执行主逻辑
                log.info(String.format("{%s}加锁成功", lockName));
                return proceeding.proceed();

            } else {
                // 如果重试次数为零, 则不重试
                if (retryNum <= 0) {
                    log.info(String.format("{%s}已经被锁, 不重试", lockName));
                    throw new BaseException(ResultCode.SYSTEM_BUSY);
                }

                if (retryWait == 0) {
                    retryWait = 200L;
                }
                // 设置失败次数计数器, 当到达指定次数时, 返回失败
                int failCount = 1;
                while (failCount <= retryNum) {
                    // 等待指定时间ms
                    Thread.sleep(retryWait);
                    if (lock.tryLock(lockWait, autoUnlockTime, TimeUnit.SECONDS)) {
                        // 执行主逻辑
                        return proceeding.proceed();
                    } else {
                        log.info(String.format("{%s}已经被锁, 正在重试[ %s/%s ],重试间隔{%s}毫秒", lockName, failCount, retryNum,
                                retryWait));
                        failCount++;
                    }
                }
                throw new BaseException(ResultCode.SYSTEM_BUSY);
            }
        } catch (Throwable throwable) {
            log.error(String.format("执行分布式锁发生异常锁名:{%s},异常名称:{%s}", lockName, throwable.getMessage()), throwable);
            throw throwable;
        } finally {
            lock.unlock();
            log.info(String.format("{%s}释放锁成功", lockName));
        }
    }

    /**
     * 获取锁参数
     *
     * @param proceeding
     * @return
     */
    private Map<String, Object> getAnnotationArgs(ProceedingJoinPoint proceeding, RedisLock redisLock) throws Exception {

        //获取方法的参数名和参数值
        MethodSignature methodSignature = (MethodSignature) proceeding.getSignature();
        List<String> paramNameList = Arrays.asList(methodSignature.getParameterNames());
        List<Object> paramList = Arrays.asList(proceeding.getArgs());

        //将方法的参数名和参数值一一对应的放入上下文中
        EvaluationContext ctx = new StandardEvaluationContext();
        for (int i = 0; i < paramNameList.size(); i++) {
            ctx.setVariable(paramNameList.get(i), paramList.get(i));
        }

        // 解析SpEL表达式获取结果
        String value = spelParser.parseExpression(redisLock.lockParameter()).getValue(ctx).toString();
//        //获取 sensitiveOverride 这个代理实例所持有的 InvocationHandler
//        InvocationHandler invocationHandler = Proxy.getInvocationHandler(redisLock);
//        // 获取 invocationHandler 的 memberValues 字段
//        Field hField = invocationHandler.getClass().getDeclaredField("memberValues");
//        // 因为这个字段是 private final 修饰,所以要打开权限
//        hField.setAccessible(true);
//        // 获取 memberValues
//        Map memberValues = (Map) hField.get(invocationHandler);
//        // 修改 value 属性值
//        memberValues.put("userType", Integer.parseInt(value));


        // 解析SpEL表达式获取结果
        Map<String, Object> result = new HashMap<String, Object>();

        result.put(LOCK_NAME, redisLock.lockPrefix() + value);
        result.put(lOCK_WAIT, redisLock.lockWait());
        result.put(AUTO_UNLOCK_TIME, redisLock.autoUnlockTime());
        result.put(RETRY_NUM, redisLock.retryNum());
        result.put(RETRY_WAIT, redisLock.retryWait());
        return result;
    }
}

注解使用

这里用的是spel表达式哦

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    @RedisLock(lockPrefix = "bim:console:group:lock:", lockParameter = "#groupMemberOperationParam.getGroupId()")
    public void groupAddMember(GroupMemberOperationParam groupMemberOperationParam) {
        
    }

大体就是这样,因为公司规定,已经把公司域名的包删除了,大家粘贴的时候将报错地方换成自己的使用就好!

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis通过自定义注解来实现分布式锁。在代码中,我们可以看到使用了一个名为`@RedisLockAnnotation`的注解\[3\]。这个注解被用于标记需要加锁的方法。在注解的定义中,可以指定锁的类型和锁的过期时间。通过在切面中定义一个`@Pointcut`,来拦截使用了`@RedisLockAnnotation`注解的方法\[2\]。在拦截到这些方法后,会执行分布式锁的逻辑。具体的实现是通过Redisson来获取分布式锁对象,并使用`tryLock`方法来尝试获取锁\[1\]。如果成功获取到锁,就可以执行业务逻辑;如果获取锁失败,则需要等待或执行其他逻辑。这样,通过注解和切面的配合,我们可以方便地在需要的地方实现分布式锁的功能。 #### 引用[.reference_title] - *1* [尚品汇之通过自定义注解实现分布式锁](https://blog.csdn.net/weixin_65627952/article/details/128188867)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [使用注解实现REDIS分布式锁](https://blog.csdn.net/LinkSLA/article/details/130419280)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值