zoukankan      html  css  js  c++  java
  • 基于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秒

     

  • 相关阅读:
    【WEB前端开发最佳实践系列】高可读的HTML
    【Web前端开发最佳实践系列】标准的HTML代码
    Web服务器配置Gzip压缩提升网站性能
    【Web前端开发最佳实践系列】前端代码推荐和建议
    【前端系列】移动前端开发之viewport的深入理解
    【Spring Boot && Spring Cloud系列】那些Spring Boot中踩过的坑
    【Spring Boot && Spring Cloud系列】Spring Boot的启动器Starter
    【Spring Boot&&Spring Cloud系列】提高数据库访问性能
    【Spring Boot&& Spring Cloud系列】单点登录SSO之OAuth2官方开发文档翻译
    【Spring Boot&& Spring Cloud系列】单点登录SSO概述
  • 原文地址:https://www.cnblogs.com/yun965861480/p/15099964.html
Copyright © 2011-2022 走看看