当前位置: 移动技术网 > IT编程>开发语言>Java > Redisson实现分布式锁(二)

Redisson实现分布式锁(二)

2019年03月20日  | 移动技术网IT编程  | 我要评论

本次基于注解+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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网