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