zoukankan      html  css  js  c++  java
  • Redisson实现分布式锁(二)

    本次基于注解+AOP实现分布式锁(招式与前文基于注解切换多数据源相同),话不多说,直接上样例:

    首先自定义注解:设计时需要考虑锁的一般属性:keys,最大等待时间,超时时间,时间单位。

    package com.paic.phssp.springtest.redisson;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import java.util.concurrent.TimeUnit;
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface RequestLockable {
        String[] key() default "";
    
        long maximumWaiteTime() default 2000;
    
        long expirationTime() default 1000;
    
        TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
    }

    新建一个抽象请求拦截器,设计模式:装饰模式,父类决定整体流程,具体细节交给字类实现,便于解耦扩展。

    package com.paic.phssp.springtest.redisson;
    
    import org.apache.commons.lang3.StringUtils;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.Signature;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
    import org.springframework.expression.EvaluationContext;
    import org.springframework.expression.Expression;
    import org.springframework.expression.ExpressionParser;
    import org.springframework.expression.common.TemplateParserContext;
    import org.springframework.expression.spel.standard.SpelExpressionParser;
    import org.springframework.expression.spel.support.StandardEvaluationContext;
    
    import java.lang.reflect.Method;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Objects;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    import java.util.stream.Collectors;
    
    public abstract class AbstractRequestLockInterceptor {
        protected abstract Lock getLock(String key);
    
        protected abstract boolean tryLock(long waitTime, long leaseTime, TimeUnit unit, Lock lock) throws InterruptedException;
    
        private static final String[] removeSigs = new String[]{"#","{","}"};
    
        @Around("@annotation(RequestLockable)")
        public Object doAround(ProceedingJoinPoint point) throws Throwable {
            //获取连接点的方法签名对象
            Signature signature = point.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
    
            Method method = methodSignature.getMethod();
            String methodName = signature.getName();
    
            //获取连接点所在的目标对象
            String targetName = point.getTarget().getClass().getName();
    
            //获取连接点方法运行时的入参列表
            Object[] arguments = point.getArgs();
    
            if (method != null && method.isAnnotationPresent(RequestLockable.class)) {
                RequestLockable requestLockable = method.getAnnotation(RequestLockable.class);
    
                String requestLockKey = getLockBySpellKey(method, targetName, methodName, requestLockable.key(), arguments);
    
                System.out.println(">>>>requestLockKey="+requestLockKey);
    
                Lock lock = this.getLock(requestLockKey);
    
                boolean isLock = this.tryLock(requestLockable.maximumWaiteTime(), requestLockable.expirationTime(),
                        requestLockable.timeUnit(), lock);
                if (isLock) {
                    try {
                        return point.proceed();
                    } finally {
                        //释放锁资源
                        lock.unlock();
                    }
                } else {
                    throw new RuntimeException("获取锁资源失败");
                }
            }
    
            //通过反射执行目标对象的连接点处的方法
            return point.proceed();
        }
    
        /**
         * 组装lock key
         *
         * @param method
         * @param targetName 对象名
         * @param methodName 方法名
         * @param keys       注解key
         * @param arguments  方法参数
         * @return
         */
        private String getLockBySpellKey(Method method, String targetName, String methodName, String[] keys, Object[] arguments) {
            StringBuilder lockKey = new StringBuilder();
            lockKey.append("lock.").append(targetName).append(".").append(methodName);
    
            if (keys != null) {
                //Joiner  Guava包
                //String keyStr = Joiner.on(".").skipNulls().join(keys);
                String keyStr = Arrays.stream(keys).filter(Objects::nonNull).collect(Collectors.joining("."));
                System.out.println("RequestLockable:keys="+keyStr);
    
                if (!StringUtils.isBlank(keyStr)) {
                    LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
                    String[] parameters = discoverer.getParameterNames(method);
    
                    //用Spell方法容易出问题,所以这里加了一些限制
                    keyStr = creatSpellExpressionStr(keyStr,parameters,removeSigs);
    
                    int length = parameters.length;
                    if(length > 0){
                        if(!hasParameters(keyStr,parameters)){
                            //不包含参数直接用keyStr
                            lockKey.append("#").append(keyStr);
                        }else{
                            //keyStr 是否包含参数名,如果包含可用Spell表达式
                            //用Spell方法容易出问题,所以这里加工下
                            keyStr = creatSpellExpressionStr(keyStr,parameters,removeSigs);
                            ExpressionParser parser = new SpelExpressionParser();
                            EvaluationContext context = new StandardEvaluationContext();
                            Map<String,Object> vMap = new HashMap<String,Object>();
                            for (int i = 0; i < length; i++) {
                                //key:方法参数名,val:值
                                vMap.put(parameters[i],arguments[i]);
                            }
                            ((StandardEvaluationContext) context).setVariables(vMap);
    
                            //eg:#{#proKey}.asd#{#proKey}fa.#{#proId}#{#proKey}  -> product.asdproductfa.123product
                            Expression expression = parser.parseExpression(keyStr,new TemplateParserContext());
                            String keysValue = expression.getValue(context, String.class);
                            lockKey.append("#").append(keysValue);
                        }
                    }
                }
            }
            return lockKey.toString();
        }
    
        private boolean hasParameters(String lockKey,String[] parameters){
            boolean hasFlag = false;
            for(String str : parameters){
                if(StringUtils.indexOf(lockKey,str) != -1){
                    hasFlag = true;
                    break;
                }
            }
            return hasFlag;
        }
    
        private String creatSpellExpressionStr(String lockKey,String[] parameters,String[] removeSigs){
            //去掉#{}等字符
            for(String sig : removeSigs){
                lockKey = StringUtils.replace(lockKey,sig,"");
            }
    
            for(String str : parameters){
                String repStr = "#{#"+str+"}";
                lockKey = StringUtils.replace(lockKey,str,repStr);
            }
            return lockKey;
        }
    
    }

    具体实现拦截类:

    package com.paic.phssp.springtest.redisson;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.redisson.Redisson;
    import org.redisson.api.RLock;
    import org.springframework.stereotype.Component;
    
    import javax.annotation.Resource;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    
    @Aspect
    @Component
    public class RedisRequestLockInterceptor extends AbstractRequestLockInterceptor {
        @Resource
        private Redisson redisson;
    
        @Override
        protected Lock getLock(String key) {
            return redisson.getLock(key);
        }
    
        @Override
        protected boolean tryLock(long waitTime, long leaseTime, TimeUnit unit, Lock lock) throws InterruptedException {
            return ((RLock) lock).tryLock(waitTime, leaseTime, unit);
        }
    }
    ProductService.java
     /**
         * 注意:key={"#proKey","#proId"} 与参数名一致时走Spell表达式
         * @param proKey
         * @param proId
         */
        @RequestLockable(key={"#{#proKey}","#{#proId}"}) //细化到参数,参数值不同时,不共享一个锁,相同时才会共享,这点要注意
        //@RequestLockable(key={"#lock_key_prod"})
        public void anotationProd(String proKey,int proId){
            String productId = stringRedisTemplate.opsForValue().get(proKey);
            int sprodId = Integer.parseInt(productId);
            if (sprodId > 0) {
                stringRedisTemplate.opsForValue().set("product", --sprodId + "");
                System.out.println(">>>>>>"+Thread.currentThread().getName() + ",product:" + sprodId + "");
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    单元测试(比较懒,就不另外起工程测了,开多线程...)

    @Test
        public void testAnotationProd(){
            //开启2个线程
            Thread thread1 = new Thread(()-> productService.anotationProd("product",1));
            Thread thread2 = new Thread(()-> productService.anotationProd("product",1));
    
            thread1.start();
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            thread2.start();
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    场景1:方法加注释:@RequestLockable(key={"#{#proKey}","#{#proId}"})

    RequestLockable:keys=#{#proKey}.#{#proId}
    >>>>requestLockKey=lock.com.paic.phssp.springtest.redisson.ProductService.anotationProd#product.1
    RequestLockable:keys=#{#proKey}.#{#proId}
    >>>>requestLockKey=lock.com.paic.phssp.springtest.redisson.ProductService.anotationProd#product.1
    >>>>>>Thread-10,product:92
    >>>>>>Thread-9,product:91

    场景2:方法加注释:@RequestLockable(key={"#lock_key_prod"})
    RequestLockable:keys=#lock_key_prod
    >>>>requestLockKey=lock.com.paic.phssp.springtest.redisson.ProductService.anotationProd#lock_key_prod
    RequestLockable:keys=#lock_key_prod
    >>>>requestLockKey=lock.com.paic.phssp.springtest.redisson.ProductService.anotationProd#lock_key_prod
    >>>>>>Thread-9,product:90
    >>>>>>Thread-10,product:89

    场景3:方法不加注释,此时两个线程同时去读写了
    >>>>>>Thread-9,product:88
    >>>>>>Thread-10,product:88

    场景4:测试另一个线程,参数proId=2,发现两个线程同时去读写了
    RequestLockable:keys=#{#proKey}.#{#proId}
    >>>>requestLockKey=lock.com.paic.phssp.springtest.redisson.ProductService.anotationProd#product.1
    RequestLockable:keys=#{#proKey}.#{#proId}
    >>>>requestLockKey=lock.com.paic.phssp.springtest.redisson.ProductService.anotationProd#product.2
    >>>>>>Thread-9,product:87
    >>>>>>Thread-10,product:87

    参考:

    https://blog.csdn.net/qq_15427331/article/details/54630999

  • 相关阅读:
    环境搭建:Vue环境搭建和项目初始化(windows)
    文件扩展关联命令
    关闭任务栏上右键的打开历史记录
    CDN基本原理和功能浅析
    制作支持UEFI启动的原装系统安装盘
    文件被占用如何查看
    BIOS和CMOS的区别
    PKI公钥基础设施简介
    网络安全通信https工作原理
    常见加密算法简介
  • 原文地址:https://www.cnblogs.com/xiaozhuanfeng/p/10560577.html
Copyright © 2011-2022 走看看