通过注解的方式实现分布式锁

286 阅读1分钟

实际项目中经常会存在方法被重复执行的问题,或者同一个资源同时被多个用户操作的问题,如果这个资源不可以同时被多个请求操作,则需要锁住资源。

一般情况下我们会使用synchronized关键字,或者ReentrantLock类,redis等方式来实现锁资源的目的,但是这样做有一个不方便的地方就是,我在每一个方法都要重复的编写很多繁杂的代码

比如:

synchronized(user.userId){
    ******
}

下面是通过注解的方式实现细粒度锁 注解

/**
* 锁
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lock {

   /**
    * 锁定key 业务键
    */
   String[] lockKey() default LOCK_KEY;


   /**
    * 获取锁失败报错信息
    */
   String errorMsg() default "您手速太快了!";
}

自定义异常

public class LockException extends RuntimeException{

   public LockException () {
       super();
   }

   public LockException (String message) {
       super(message);
   }

   public LockException (String message, Throwable cause) {
       super(message, cause);
   }

   public LockException (Throwable cause) {
       super(cause);
   }

   protected LockException (String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
       super(message, cause, enableSuppression, writableStackTrace);
   }
}

Aop切面

@Aspect
@Component
public class LockAspect {

   private ParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

   private static ThreadLocal<String> lockKey = new ThreadLocal<>();

   private ExpressionParser parser = new SpelExpressionParser();

   @Pointcut("@annotation(Lock)")
   public void lockFilter() {

   }

   @Before("lockFilter()")
   public void dataFilter(JoinPoint point) throws Throwable {
       MethodSignature signature = (MethodSignature) point.getSignature();
       Lock lock = signature.getMethod().getAnnotation(Lock.class);
       String keyName = getKeyName(point,lock);
       String redisKey = REDIS_LOCK_PREFIX+keyName;
       RedisUtils redisUtils = SpringContextUtils.getBean(RedisUtils.class);
       boolean result = redisUtils.setNx(redisKey,"1");
       if(!result){
           throw new LockException(lock.errorMsg());
       }
       //设置最高20秒的过期时间
       redisUtils.expire(redisKey,20, TimeUnit.SECONDS);
       try {
           lockKey.set(redisKey);
       }catch (Exception e){
           e.printStackTrace();
       }
   }

   /**
    * 释放锁
    */
   @AfterReturning("lockFilter()")
   public void afterFilter(JoinPoint point){
       releaseLock(point);
   }

   /**
    * 释放锁
    */
   @After("lockFilter()")
   public void after(JoinPoint point){
       releaseLock(point);
   }

   /**
    * 释放锁
    */
   @AfterThrowing(value = "lockFilter()",throwing = "ex")
   public void throwFilter(JoinPoint point,Throwable ex){
       if(ex instanceof LockException){
           //如果是锁本身的异常不需要释放锁
           return;
       }
       releaseLock(point);
   }


   private void releaseLock(JoinPoint point){
       MethodSignature signature = (MethodSignature) point.getSignature();
       Lock lock = signature.getMethod().getAnnotation(Lock.class);
       String keyName = getKeyName(point,lock);
       String redisKey = REDIS_LOCK_PREFIX+keyName;
       RedisUtils redisUtils = SpringContextUtils.getBean(RedisUtils.class);
       redisUtils.delete(redisKey);
       String key = lockKey.get();
       if(!ObjectUtils.isEmpty(key)){
           redisUtils.delete(key);
       }
   }

   private String getKeyName(JoinPoint joinPoint,Lock lock){
       Object[] args = joinPoint.getArgs();
       String[] keys = lock.lockKey();
       Method method = getMethod(joinPoint);
       List<String> definitionKeyList = new ArrayList<>();
       for (String definitionKey : keys) {
           if (!ObjectUtils.isEmpty(definitionKey)) {
               EvaluationContext context = new MethodBasedEvaluationContext(null, method, args, nameDiscoverer);
               Object objKey = parser.parseExpression(definitionKey).getValue(context);
               definitionKeyList.add(ObjectUtils.nullSafeToString(objKey));
           }
       }
       return method.getName()+"-"+ StringUtils.join(definitionKeyList,"-");
   }


   private Method getMethod(JoinPoint joinPoint) {
       MethodSignature signature = (MethodSignature) joinPoint.getSignature();
       Method method = signature.getMethod();
       if (method.getDeclaringClass().isInterface()) {
           try {
               method = joinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(),
                       method.getParameterTypes());
           } catch (Exception e) {
               e.printStackTrace();
           }
       }
       return method;
   }
}

使用

@Override
@Lock(lockKey = {"#user.userId","#user.userName"})
public Integer addUserTest1(User user) throws InterruptedException {
    Thread.sleep(1000);
    System.out.println("方法addUserTest1被调用");
    throw new RuntimeException("有异常");
}