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

发布时间 2023-07-04 10:36:06作者: C紫枫

场景概述

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

实现

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

import java.lang.annotation.*;

@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.MILLISECONDS; // 时间单位
}

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

@Aspect
@Component
@Order(1) // 设置切面的优先级,确保分布式锁注解在事务注解之前执行
public class DistributedLockAspect {

    private final RedissonClient redissonClient;

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

    @Around("@annotation(distributedLock)")
    public Object applyLock(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
        String lockName = distributedLock.value();
        RLock lock = redissonClient.getLock(lockName);
        long waitTime = distributedLock.waitTime();
        long leaseTime = distributedLock.leaseTime();
        TimeUnit timeUnit = distributedLock.timeUnit();

        boolean isLocked = false;
        try {
            isLocked = lock.tryLock(waitTime, leaseTime, timeUnit);
            if (isLocked) {
                return joinPoint.proceed();
            } else {
                throw new RuntimeException("Failed to acquire lock");
            }
        } finally {
            if (isLocked) {
                    // 是否是当前执行线程的锁
                    if(lock.isHeldByCurrentThread()){ 
                       // 释放锁
                        lock.unlock();
                    }
            }
        }
    }
}

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

@Service
public class MyService {

    @DistributedLock(value ="test" ,waitTime = 5, leaseTime = 10, timeUnit = TimeUnit.SECONDS)
    public void myMethod() {
        // 加锁后的业务逻辑
    }
}

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

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

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

@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE) // 设置切面的优先级为最高
public class DistributedLockAspect {

    private RedissonClient redissonClient;

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

    @Around("@annotation(distributedLock)")
    public Object applyLock(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
        RLock lock = redissonClient.getLock("lockName");
        long waitTime = distributedLock.waitTime();
        long leaseTime = distributedLock.leaseTime();
        TimeUnit timeUnit = distributedLock.timeUnit();

        boolean isLocked = false;
        try {
            isLocked = lock.tryLock(waitTime, leaseTime, timeUnit);
            if (isLocked) {
                return joinPoint.proceed();
            } else {
                throw new RuntimeException("Failed to acquire lock");
            }
        } finally {
            if (isLocked) {
                    // 是否是当前执行线程的锁
                    if(lock.isHeldByCurrentThread()){ 
                       // 释放锁
                        lock.unlock();
                    }
            }
        }
    }
}

在上述代码中,通过@Order(Ordered.HIGHEST_PRECEDENCE)设置了切面的优先级为最高,确保它在其他切面之前执行。