基于redis分布式锁注解实现

基于redis分布式锁注解实现

  • 1、编写注解

  • 2、编写切面

  • 3、如何使用

1、编写注解

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;

@Target(ElementType.METHOD)  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
public @interface RedisLock {
    
    String key();
    
    // 并发锁key
    String value();
    
    // 锁定时长 默认单位秒
    long ttl() default 5;

}
 

2、编写切面

import java.lang.reflect.Method;
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.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import com.yun.common.api.redis.RedisService;
import com.yun.common.base.response.BusinessException;
import com.yun.common.base.utils.AssertUtil;

import lombok.extern.slf4j.Slf4j;

@Aspect
@Component
@Slf4j
public class RedisLockAspect {
    
    @Autowired
    private RedisService redisService;

    @Pointcut("execution(public * com.yun.*.app.*.service..*.*(..))")
    public void appPointCut(){};
    
    @Pointcut("execution(public * com.yun.*.domain..*.*(..))")
    public void domainPointCut(){};

    @Around("(appPointCut() && @annotation(redisLock)) || (domainPointCut() && @annotation(redisLock)))")
    public Object before(ProceedingJoinPoint pJoinPoint, RedisLock redisLock) throws Throwable {
        String key = redisLock.key();
        String value = redisLock.value();
        long time = redisLock.ttl();
        
        AssertUtil.notNull(key, "获取并发锁key为空。", key);
        AssertUtil.notNull(value, "获取并发锁value为空。", value);

        value = this.getRedisKey(pJoinPoint, value);
        String lockKey = key.concat(value);
        
        // 1、获取锁
        RLock lock = redisService.getRLock(lockKey);
        // 2、锁定
        try {
            AssertUtil.isTrue(lock.tryLock(time, TimeUnit.SECONDS), "获取并发锁[%s]失败。", lockKey);
            // 3、业务逻辑
            return pJoinPoint.proceed();
        } catch (BusinessException e) {
            log.error("获取锁失败。", e);
            throw e;
        }finally {
            // 4、释放锁
            lock.unlock();
        }
        
    }

    private String getRedisKey(ProceedingJoinPoint pJoinPoint, String key) {
        //使用SpringEL表达式解析注解上的key
        SpelExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(key);
        //获取方法入参
        Object[] parameterValues = pJoinPoint.getArgs();
        //获取方法形参
        MethodSignature signature = (MethodSignature)pJoinPoint.getSignature();
        Method method = signature.getMethod();
        DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
        String[] parameterNames = nameDiscoverer.getParameterNames(method);
        if (parameterNames == null || parameterNames.length == 0) {
            //方法没有入参,直接返回注解上的key
            return key;
        }
        //解析表达式
        EvaluationContext evaluationContext = new StandardEvaluationContext();
        // 给上下文赋值
        for(int i = 0 ; i < parameterNames.length ; i++) {
            evaluationContext.setVariable(parameterNames[i], parameterValues[i]);
        }
        try {
            Object expressionValue = expression.getValue(evaluationContext);
            if (expressionValue != null && !"".equals(expressionValue.toString())) {
                //返回el解析后的key
                return expressionValue.toString();
            }else{
                //使用注解上的key
                return key;
            }
        } catch (Exception e) {
            //解析失败,默认使用注解上的key
            return key;
        }

    }
    
}

3、如何使用

3.1、方式一:编码方式

  • 注入RedisService

  • 应用分布式锁

适用场景:逻辑复杂,长事务场景。

注意:

1、存在第三方调用逻辑时,必须指定超时时间,且超时时间必须小于锁定时间。

2、应尽量提炼业务,缩短锁定范围。

3、合理设置锁定时间,避免出现锁超时的情况。

3.1.1、注入RedisService

@Autowired
protected RedisService redisService;

 

3.1.2、应用分布式并发锁

        // 1、获取锁
      RLock lock = redisService.getRLock(key);
      // 2、锁定
  AssertUtil.isTrue(lock.tryLock(20, TimeUnit.SECONDS), ResultEnum.DATA_LOCKED);
  try {
  //TODO 3、业务逻辑
  } catch (Exception e) {
log.error("业务异常", e);
} finally {
// 4、释放锁
lock.unlock();
}

 

3.2、方式二:注解方式

  • 添加注解

适用场景:逻辑简单,耗时短。

注意:合理设置锁定时间,避免出现锁超时的情况。

如下:

    @RedisLock(key = ProductRedisKey.TEST_KEY, value = "#listProductQuery.orderNo", ttl = 20)
@Override
public List<ProductVO> list(ListProductQuery listProductQuery) {

// TODO 业务逻辑

return BeanCopierUtil.copyPropertiesOfList(
productDubboService
.list(BeanCopierUtil.copyProperties(listProductQuery, ListProductDTO.class)),
ProductVO.class);
}

参数解释:

@RedisLock注解可以添加在应用端的 com.yun.*.app.*.service 下

可以添加在业务端的 com.yun.*.domain 下

key 表示锁的目录,比如“system:test:”

value为锁定的具体业务单据号"123456",可以使用el表达式,比如 #listProductQuery.orderNo

ttl为锁定时间,默认为5秒

 

原文地址:https://www.cnblogs.com/yun965861480/p/15099964.html