• 使用Spring的表达式解析器+Redis实现分布式锁


    使用Spring的表达式解析器+Redis实现分布式锁

    怎么配置使用Redis就不在这里说明了!!!

    AOP依赖

    		
    		<dependency>
    			<groupId>org.springframework.bootgroupId>
    			<artifactId>spring-boot-starter-aopartifactId>
    		dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    1.0 定义分布式锁注解

    /**
     * @Author: ZhiHao
     * @Date: 2022/7/28 20:19
     * @Description: 分布式锁注解
     * @Versions 1.0
     **/
    @Documented
    @Target(value = {ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DistributedLock {
    
        /**
         * 锁前缀
         *
         * @return
         */
        String lockKeyPrefix() default "";
    
        /**
         * 为空默认以用户维度加锁
         * 锁Key参数解析表达式 (自定义实体是 User user => #user.id 、 
         * |  Map params   => #params['phone']
         * |  string、int    params   => #params
         * |  多个参数   => 'id_'+#user.id+'age_'+#user.age
         *
         * @return
         */
        String lockKeyValue();
    
        /**
         * 锁住时间 (秒)
         * @return
         */
        double lockTime() default 1.5;
    
        /**
         * 加锁失败抛异常的提示
         * @return
         */
        LockFailTips lockFailTips() default LockFailTips.DEFAULT_VALUE;
        
          /**
         * 添加用户 masterId_12345  标识
         * @return
         */
        boolean addLockKeyUserFlag() default false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    2.0 失败抛异常提示枚举

    public enum LockFailTips {
    
        ORDER_SUBMIT(()->{throw  new RuntimeException("订单提交点击太快了!");},"订单信息提交"),
        
        DEFAULT_VALUE(()->{throw  new RuntimeException("加分布式锁失败!");},"默认提示"),
        ;
        private Supplier<?> supplier;
        private String describe;
    
        LockFailTips(Supplier<?> supplier, String describe) {
            this.supplier = supplier;
            this.describe = describe;
        }
    
        public Supplier<?> getSupplier() {
            return supplier;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3.0 注解式AOP实现

    /**
     * @Author: ZhiHao
     * @Date: 2022/7/28 20:07
     * @Description:
     * @Versions 1.0
     **/
    @Aspect
    @Service
    @Slf4j
    public class RedisDistributedLock implements Ordered {
    
        @Autowired
        private RedisTemplate<String, String> redisTemplate;
        
        @Autowired
        private HttpServletRequest request;
    
        private static final String PROJECT_NAME = "order-api:";
    
        private static final String LOCK_SCRIPT = "if redis.call('setnx', KEYS[1], ARGV[1] ) == 1 then return redis.call('pexpire', KEYS[1], tonumber(ARGV[2])) else return 0 end";
    
        private static final String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    
        private static final DefaultRedisScript<Long> lock_script;
        private static final DefaultRedisScript<Long> release_lock_script;
    
        // 表达式解析器
        private static final ExpressionParser expressionParser = new SpelExpressionParser();
        // 类方法参数命名获取, 底层是通过读取文件解析获取到的
        private static final LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer
                = new LocalVariableTableParameterNameDiscoverer();
    
        static {
            lock_script = new DefaultRedisScript<>();
            lock_script.setScriptText(LOCK_SCRIPT);
            lock_script.setResultType(Long.class);
    
            release_lock_script = new DefaultRedisScript<>();
            release_lock_script.setScriptText(RELEASE_LOCK_SCRIPT);
            release_lock_script.setResultType(Long.class);
        }
    
        @Pointcut("@annotation(com.zhihao.annotation.DistributedLock)")
        private void expression() {
        }
    
        /**
         * 使用环谣通知
         * @Before("expression()") => @Before("@annotation(distributedLock)") 可以直接方法参数定义使用注解
         * @param joinPoint
         * @param distributedLock
         * @author: ZhiHao
         * @date: 2022/7/28
         * @return
         */
        @Around("@annotation(distributedLock)")
        public Object lock(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
            // 锁的值
            String value = UUID.randomUUID().toString();
            // 获取锁的key
            String lockKey = this.getLockKey(joinPoint,distributedLock);
            if (log.isDebugEnabled()){
                log.debug("distributedLock===requestUrl:{}, lockKey:{}, requestId:{}",request.getServletPath(),lockKey,requestId);
            }
            try {
                // 加锁
                Boolean lock = this.distributedLock(lockKey,value, distributedLock);
                if (!lock){
                    // 加锁失败抛出异常
                    distributedLock.lockFailTips().getSupplier().get();
                }
                // 执行被切面方法
                return joinPoint.proceed(joinPoint.getArgs());
            } finally {
                // 解锁
                redisTemplate.execute(release_lock_script, Collections.singletonList(lockKey), value);
            }
        }
    
        /**
         * 加锁
         *
         * @param lockKey
         * @param value
         * @param distributedLock
         * @return java.lang.Boolean true=成功
         * @author: ZhiHao
         * @date: 2022/7/28
         */
        private Boolean distributedLock(String lockKey, String value, DistributedLock distributedLock) {
            double lockTime = distributedLock.lockTime() * 1000;
            Long result = redisTemplate.execute(lock_script, Collections.singletonList(lockKey), value, String.valueOf(lockTime));
            return Optional.ofNullable(result).orElse(0L).equals(1L);
        }
    
        /**
         * 获取Key
         *
         * @param joinPoint
         * @param distributedLock
         * @return java.lang.String
         * @author: ZhiHao
         * @date: 2022/7/28
         */
        private String getLockKey(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) {
            StringBuilder keyBuilder = new StringBuilder(PROJECT_NAME);
            // 获取注解数据
            String keyPrefix = distributedLock.lockKeyPrefix();
            String keyValue = distributedLock.lockKeyValue();
    
            keyBuilder.append(keyPrefix);
    
            // 获取方法
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            // 获取参数
            Object[] args = joinPoint.getArgs();
            // 获取方法
            Method targetMethod = methodSignature.getMethod();
            // 获取方法参数名称
            String[] parameterNames = parameterNameDiscoverer.getParameterNames(targetMethod);
        
            // 如果表达式为空, 或者表达式不为空, 参数为空, 使用方法名称 作为Key
            Integer length = Optional.ofNullable(args).map(el -> el.length).orElse(0);
            if (StrUtil.isBlank(keyValue) || (StrUtil.isNotBlank(keyValue) && length <= 0)) {
                return keyBuilder.append(keyValue).append(targetMethod.getName()).toString().trim();
            }
            
            // 封装表达式变量和参数
            StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
            for (int i = 0; i < args.length; i++) {
                evaluationContext.setVariable(parameterNames[i], args[i]);
            }
            // 解析表达式
            Expression expression = expressionParser.parseExpression(keyValue);
            String value = expression.getValue(evaluationContext, String.class);
            // 拼接最终key返回
            return keyBuilder.append(value).toString();
        }
        
        // AOP优先级最高
        @Override
        public int getOrder() {
            return Ordered.HIGHEST_PRECEDENCE+1;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145

    4.0 使用

    @PostMapping("/post")
    // 取user里面的age属性setVariable(user, user)
    @DistributedLock(lockKeyPrefix = "order_submit:",lockKeyValue = "#user.age",   
            lockFailTips = LockFailTips.ORDER_SUBMIT,LockTime = 5.5)
    public User post(@RequestBody @Valid User user) {
        System.out.println(user);
        return user;
    }
    
    //-------------------------
    @GetMapping("/gets")
    // 取name属性setVariable(name, args[i])
    @DistributedLock(lockKeyPrefix = "order_submitsss:",lockKeyValue = "#name",
                     lockFailTips = LockFailTips.ORDER_SUBMIT,LockTime = 5.5)
    public User gets(String name) {
    
    }
    
    //-------------多个参数Map--------------
    @DistributedLock(lockKeyPrefix = "superTeamManage_lockKey:memberInvite-", addLockKeyUserFlag = true,
                lockKeyValue = "'phone_'+#params['phone']+'age_'+#params['age']")
        @PostMapping("/memberInvite")
        public BaseDialog memberInvite(@RequestBody @NotEmpty(message = "参数不能为空!") Map<String, String> params) {
            return superTeamManageService.memberInvite(params.get("phone"));
        }
    
    //-------------多个参数自定义实体--------------
    @DistributedLock(lockKeyPrefix = "order_submit:",lockKeyValue = "'age_'+#user.age+'name_'+#user.name",   
            lockFailTips = LockFailTips.ORDER_SUBMIT,LockTime = 5.5)
    public User post(@RequestBody @Valid User user) {
        System.out.println(user);
        return user;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    如果获取的参数值为null, 表达式解析器解析出来的结果是: phone_null, 可用MVC搭配使用参数验证参数绑定, 先校验参数为空抛出异常!

    扩展:

    parameternamediscoverer 解析

    Spring的表达式解析器模式(学习笔记) 自行搜索

    StandardEvaluationContext 优化

    StandardEvaluationContext 可以更换使用 MethodBasedEvaluationContext

    private static final LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer
                = new LocalVariableTableParameterNameDiscoverer();
    // 封装表达式变量和参数
    ExpressionRootObject rootObject = new ExpressionRootObject(joinPoint.getTarget(),args);
    MethodBasedEvaluationContext evaluationContext = new MethodBasedEvaluationContext(rootObject ,targetMethod,args,parameterNameDiscoverer);
    
    // 解析表达式
    Expression expression = expressionParser.parseExpression(keyValue);
    String value = expression.getValue(evaluationContext, String.class);
    
    
    // -----------------------ExpressionRootObject--------------------------
    @Data
    public class ExpressionRootObject {
        private final Object object;
    
        private final Object[] args;
    
        public ExpressionRootObject(Object object, Object[] args) {
            this.object = object;
            this.args = args;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    优化: 缓存表达式Expression

    自定义ExpressionEvaluator 继承 CachedExpressionEvaluator

    @Component
    public class ExpressionEvaluator extends CachedExpressionEvaluator {
    
        private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<>(64);
    
        public Object key(String conditionExpression, AnnotatedElementKey elementKey, EvaluationContext evalContext) {
            // 调用父类实现的缓存表达式
            return getExpression(this.conditionCache, elementKey, conditionExpression).getValue(evalContext);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    注入使用

    @Autowired
    private ExpressionEvaluator evaluator;
    
    private String getLockKey(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) {
            StringBuilder keyBuilder = new StringBuilder(PROJECT_NAME);
            // 获取注解数据
            String keyPrefix = distributedLock.lockKeyPrefix();
            String keyValue = distributedLock.lockKeyValue();
    
            keyBuilder.append(keyPrefix);
    
            // 添加用户标识
            if (distributedLock.addLockKeyUserFlag()) {
                keyBuilder.append("masterId_").append(通过request获取).append("_");
            }
    
            // 获取方法
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            // 获取参数
            Object[] args = joinPoint.getArgs();
            // 获取方法
            Method targetMethod = methodSignature.getMethod();
            // 获取被切目标对象
            Object target = joinPoint.getTarget();
    
            // 如果表达式为空, 或者表达式不为空, 参数为空, 使用方法名称+用户标识, 作为Key
            Integer length = Optional.ofNullable(args).map(el -> el.length).orElse(0);
            if (StrUtil.isBlank(keyValue) || (StrUtil.isNotBlank(keyValue) && length <= 0)) {
                return keyBuilder.append(keyValue).append(targetMethod.getName())
                        .append("masterId_").append(通过request获取).toString().trim();
            }
    
            // 封装表达式变量和参数
            ExpressionRootObject rootObject = new ExpressionRootObject(target,args);
            MethodBasedEvaluationContext evaluationContext = new MethodBasedEvaluationContext(rootObject ,targetMethod,args,parameterNameDiscoverer);
    
            // 解析表达式
            AnnotatedElementKey methodKey = new AnnotatedElementKey(targetMethod, target.getClass());
            Object value = evaluator.key(keyValue, methodKey, evaluationContext);
            // 拼接最终key返回
            return keyBuilder.append(value).toString();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    1

  • 相关阅读:
    centOS7管理开放防火墙端口
    美颜SDK全身美颜是基于什么技术实现的?
    【前端】[vue3] [uni-app] 组件样式击穿:deep
    粒径测量中的D10 D50 D90的含义
    windows 启用对TLS1.2和1.3的支持,并禁用对TLS1.0的支持
    webrtc终极版(二)搭建自己的iceserver服务,并用到RTCMultiConnection的demo中
    Java中的异常以及异常处理
    ES6 -- 模块化(CommonJS、AMD、ES Module)
    38.红黑树(王道第7章查找补充知识)
    学习笔记17--国内网联车上路的测试政策
  • 原文地址:https://blog.csdn.net/weixin_44600430/article/details/126116305