【Redisson】基于自定义注解的Redisson分布式锁实现

5 篇文章 0 订阅
2 篇文章 0 订阅

前言

在项目中,经常需要使用Redisson分布式锁来保证并发操作的安全性。在未引入基于注解的分布式锁之前,我们需要手动编写获取锁、判断锁、释放锁的逻辑,导致代码重复且冗长。为了简化这一过程,我们引入了基于注解的分布式锁,通过一个注解就可以实现获取锁、判断锁、处理完成后释放锁的逻辑。这样可以大大简化代码,提高开发效率。

目标

使用@DistributedLock即可实现获取锁,判断锁,处理完成后释放锁的逻辑。
引入注解后的加锁示例:

@RestController
public class HelloController {

  @DistributedLock
  @GetMapping("/helloWorld")
  public void helloWorld() throws InterruptedException {
    System.out.println("helloWorld");
    Thread.sleep(100000);
  }
}

涉及知识

  • SpringBoot
  • Spring AOP
  • Redisson
  • 自定义注解
  • 统一异常处理
  • SpEL表达式

代码实现

引入依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>org.redisson</groupId>
  <artifactId>redisson</artifactId>
  <version>3.21.3</version>
</dependency>
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
</dependency>

注解类

/**
 * 分布式锁注解
 * @author 只有影子
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {
  /**
   * 获取锁失败时,默认的错误描述
   */
  String errorDesc() default "任务正在处理中,请耐心等待";

  /**
   * SpEL表达式,用于获取锁的key
   * 示例:
   * "#name"则从方法参数中获取name的值作为key
   * "#user.id"则从方法参数中获取user对象中的id作为key
   */
  String[] keys() default {};

  /**
   * key的前缀,为空时取类名+方法名
   */
  String prefix() default "";
}

切面类

/**
 * 分布式锁切面类
 * @author 只有影子
 */
@Slf4j
@Aspect
@Component
public class DistributedLockAspect {

    @Resource
    private RedissonClient redissonClient;
    private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();

    @Around("@annotation(distributedLock)")
    public Object around(ProceedingJoinPoint joinPoint,DistributedLock distributedLock) throws Throwable {
        String redisKey = getRedisKey(joinPoint, distributedLock);
        log.info("拼接后的redisKey为:" + redisKey);
        RLock lock = redissonClient.getLock(redisKey);
        if (!lock.tryLock()) {
            // 可以使用自己的异常类,演示用RuntimeException
            throw new RuntimeException(distributedLock.errorDesc());
        }
        // 执行被切面的方法
        try {
            return joinPoint.proceed();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 动态解密参数,拼接redisKey
     * @param joinPoint
     * @param distributedLock  注解
     * @return
     */
    private String getRedisKey(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        EvaluationContext context = new MethodBasedEvaluationContext(TypedValue.NULL, method, joinPoint.getArgs(), PARAMETER_NAME_DISCOVERER);
        StringBuilder redisKey = new StringBuilder();
        // 拼接redis前缀
        if (StringUtil.isNotBlank(distributedLock.prefix())) {
            redisKey.append(distributedLock.prefix()).append(":");
        } else {
            // 获取类名
            String className = joinPoint.getTarget().getClass().getSimpleName();
            // 获取方法名
            String methodName = joinPoint.getSignature().getName();
            redisKey.append(className).append(":").append(methodName).append(":");
        }

        ExpressionParser parser = new SpelExpressionParser();
        for (String key : distributedLock.keys()) {
            // keys是个SpEL表达式
            Expression expression = parser.parseExpression(key);
            Object value = expression.getValue(context);
            redisKey.append(ObjectUtils.nullSafeToString(value));
        }
        return redisKey.toString();
    }
}

统一异常处理类

/**
 * 全局异常处理类
 * @author 只有影子
 */
@RestControllerAdvice
public class ExceptionHandle {
  @ExceptionHandler(Exception.class)
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  public String sendErrorResponseSystem(Exception e) {
    // 这里只是模拟返回值,实际项目中一般都是返回封装好的统一返回类
    return e.getMessage();
  }
}

还需要将redis配置读入,这里就不体现

使用示例

1. 无参方法或者需要加方法级的锁

@DistributedLock
@GetMapping("/helloWorld")
public void helloWorld() throws InterruptedException {
  System.out.println("helloWorld");
  Thread.sleep(100000);
}

调用接口:http://localhost:8080/helloWorld

拼接后的redisKey为:HelloController:helloWorld:

可以看到,无参方法的key为HelloController:helloWorld:,其中HelloController为类名,helloWorld为方法名,因为是无参方法,所以没有接下来的参数。

这时候,再次调用改接口,则不会再进去接口,会被切面类直接拦截,返回如下结果:

image-20231123222250866

在实际生产使用中,这种情况一般被用来在自动任务上标注,因为在集群环境中自动任务同一时间一般只需要启动一个。

2. 有参数方法,其中key从name中取值

@DistributedLock(keys = "#name")
@GetMapping("/hello1")
public String hello1(String name) throws InterruptedException {
  String s = "hello " + name;
  System.out.println(s);
  Thread.sleep(100000);
  return s;
}

调用接口为:http://localhost:8080/hello1?name=hurry

拼接后的redisKey为:HelloController:hello1:hurry

这时候,再通过hurry这个名称调用时,就不会再处理,而name换为zhangsan时,则就能正常进入接口。

这时候redis中的key为

> 127.0.0.1@6379 connected!
> keys *
HelloController:hello2:zhangsan
HelloController:hello2:hurry

实际业务中,需要根据不同的参数值进行加锁的场景。

3. 有参数方法,其中key需要从user对象中获取name

@DistributedLock(keys = "#user.name")
@GetMapping("/hello2")
public String hello2(User user) throws InterruptedException {
  String s = "hello " + user.getName();
  System.out.println(s);
  Thread.sleep(100000);
  return s;
}

需要从某个对象中获取指定属性作为key的场景

4.有参数方法,其中key从name上取值并指定前缀

@DistributedLock(keys = "#name",prefix = "testPrefix")
@GetMapping("/hello3")
public String hello3(String name) throws InterruptedException {
  String s = "hello " + name;
  System.out.println(s);
  Thread.sleep(100000);
  return s;
}

需要指定key前缀的场景

待完善功能

  • 对集合里的各个数据单独加锁
  • 支持等待的策略以及配置等待时间

最后

由于文章篇幅原因,很多东西没有深入的讲解,但是基于以上代码基本实现了基于注解的分布式锁,可以大大提到开发效率。如果还有其他需要拓展的功能,可以通过在注解类增加属性及在切面类中通过不同的属性进行不同的处理来实现。

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
自定义注解实现分布式锁是一种在分布式系统中解决并发问题的方法。通过在需要加锁的方法上添加自定义注解,可以实现对该方法的并发控制。具体实现步骤如下: 1. 首先,在项目的pom文件中引入相关的依赖,以支持分布式锁的功能。 2. 在项目中创建一个新的目录,并使用元注解来定义自定义注解。这个自定义注解可以用来标记需要加锁的方法。 3. 在具体的业务实现类中,使用自定义注解来标记需要加锁的方法。这样,在方法执行时,会根据注解的配置来进行并发控制。 使用自定义注解实现分布式锁的好处是可以避免在每个需要加锁的方法中都引入相关的配置类和方法,提高了代码的可读性和可维护性。此外,还可以将配置和项目打包成jar包,方便在其他项目中引入使用。 分布式锁相对于普通锁的区别在于,普通锁只能在单体应用中锁住方法,而分布式锁可以在分布式系统中实现并发控制。在分布式系统中,不同实例之间共用代码和数据库,因此需要使用分布式锁来保证并发操作的正确性。分布式锁可以使用一些工具,如Redis,将锁放在共享的资源中,以实现多个实例共用一个锁的效果。 总结来说,自定义注解实现分布式锁是一种方便且可扩展的方式,可以在分布式系统中解决并发问题。 #### 引用[.reference_title] - *1* [自定义注解实现分布式锁](https://blog.csdn.net/qq_37205211/article/details/122140430)[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^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [分布式编程-实现分布式锁-优雅的使用自定义注解实现](https://blog.csdn.net/qq_41692766/article/details/105842467)[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^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [自定义注解,基于redis实现分布式锁](https://blog.csdn.net/weixin_43975276/article/details/131097829)[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^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丶只有影子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值