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
上一篇: asp.net core 系列 7 Razor框架路由(上)
下一篇: 【JVM】问题排查