Fork me on GitHub

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

 

posted @ 2019-03-19 18:54  小传风  阅读(380)  评论(0编辑  收藏  举报