问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

AOP+SPel+Redis实现分布式锁的切面

发布网友 发布时间:2024-09-11 14:17

我来回答

1个回答

热心网友 时间:2024-10-20 06:56

一、背景介绍

随着高并发场景的逐渐增多,各类系统中(尤其是类似秒杀系统这种对于并发度要求很高的场景下)对于分布式锁的需求逐渐增大。

而在分布式锁的代码实现中,往往会存在如下情况

publicvoidmethod(){//业务代码try{booleanb=redisClient.tryLock(key,value);if(b){//......逻辑判断}//获取锁后所要执行的业务代码}catch(Exceptione){log.error("error");thrownewException();}finally{if(redisClient.get(key).equals(value)){redisClient.tryRelease(key);}}//业务代码}

大概写了段比较简单的代码,意思是在很多场景下,都是这样子在实现分布式锁的,具体redisClient是怎么实现的这个根据情况可以自己选择。

这段代码的问题在于,如果需要分布式锁的场景太多,那么整个系统中就会处处都存在这样的trycatchfinally的代码块,这样会使得整体代码显得十分臃肿,重复代码太多

那么怎么去解决这种使得代码过于臃肿的问题呢?

答案很简单:使用AOP技术,将加入分布式锁的部分以切面的方式剥离出业务代码中

具体怎么实现呢?

二、分布式锁注解+切面的简单实现

首先我们实现一个注解,注解命名建议要直观,让人可以一眼看出该注解的作用

@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic@interfaceRedisDistributedLock{//锁定的资源的值,即存入redis的keyStringkey()default"default_key";//锁保持时间(以毫秒为单位)intkeepMills()default1*10*1000;}

关于自定义注解的方式,本文不再赘述,有兴趣的可以自行去了解,

我们希望使用@RedisDistributedLock这个注解去实现分布式锁,即如下所示。

@RedisDistributedLock(key="key_example",keepMills=2*10*1000)publicvoidmethod(){//业务代码}

即我们想在执行method方法之前,可以对key_example加锁,如果不设置key,则默认为default_key,释放锁的时间为20秒,如果不加设置,则默认为10秒

那么具体AOP切面的简单实现如下

@Aspect@Component@Slf4jpublicclassRedisDistributedLockAspect{@AutowiredprivateRedisClientredisClient;@Around(value="@annotation(redisDistributedLock)")publicObjectinvoke(ProceedingJoinPointpoint)throwsThrowable{Signaturesignature=point.getSignature();MethodSignaturemethodSignature=(MethodSignature)signature;MethodtargetMethod=methodSignature.getMethod();//获取方法上的注解RedisDistributedLockredisLock=targetMethod.getAnnotation(RedisDistributedLock.class);Stringkey=redisLock.key();if(StringUtils.isEmpty(key)){key=point.getTarget().getClass().getName()+"."+targetMethod.getName();}BooleanisLocked=redisClient.tryLock(key,"true",redisLock.keepMills());if(!isLocked){logger.debug("getlockfailed:"+key);returnnull;}logger.debug("getlocksuccess:"+key);try{returnproceedingJoinPoint.proceed();}catch(Exceptione){logger.error("executelockedmethodoccuredanexception",e);}finally{if(redisClient.get(key,String.class).equals(value)){redisClient.tryRelease(key);}}returnnull;}}三、动态的方法入参作为分布式锁的key

那么通过这种方式就可以实现对于method()方法上锁了,但是这个样还是有点不太方便

如果我想对于method()方法传入的参数加锁而不是只能写死一个key该怎么办

如下:

@RedisDistributedLock(key="param",keepMills=2*10*1000)publicvoidmethod(Stringparam){//业务代码}

如果我们可以动态的对于方法中传入的参数加锁,那么这个注解使用起来就更加的方便了

此处我们可以使用Spel表达式来进行处理,关于SPel表达式,请各位自己去查询资料,我这里只给出了最终的代码

@Aspect@Component@Slf4jpublicclassRedisDistributedLockAspect{@AutowiredprivateRedisClientredisClient;privatestaticLoggerlogger=LoggerFactory.getLogger(RedisDistributedLockAspect.class);@Around(value="@annotation(redisDistributedLock)")publicObjectinvoke(ProceedingJoinPointproceedingJoinPoint,RedisDistributedLockredisDistributedLock)throwsThrowable{Stringkey=redisDistributedLock.key();Object[]arg=proceedingJoinPoint.getArgs();Methodmethod=((MethodSignature)proceedingJoinPoint.getSignature()).getMethod();//获取被拦截方法参数名列表(使用Spring支持类库)LocalVariableTableParameterNameDiscovererlocalVariableTable=newLocalVariableTableParameterNameDiscoverer();String[]parameterNames=localVariableTable.getParameterNames(method);//使用SPEL进行key的解析ExpressionParserparser=newSpelExpressionParser();//SPEL上下文StandardEvaluationContextcontext=newStandardEvaluationContext();//把方法参数放入SPEL上下文中for(inti=0;i<parameterNames.length;i++){context.setVariable(parameterNames[i],arg[i]);}//使用变量方式传入业务动态数据if(valueOfKey.matches("^#.*.$")){valueOfKey=parser.parseExpression(key).getValue(context,String.class);}//使用UUID生成随机数作为value,避免出现锁被错误释放的问题Stringvalue=UUID.randomUUID().toString();BooleanisLocked=redisClient.tryLock(key,value,redisDistributedLock.keepMills());if(!isLocked){logger.debug("getlockfailed:"+key);returnnull;}logger.debug("getlocksuccess:"+key);try{returnproceedingJoinPoint.proceed();}catch(Exceptione){logger.error("executelockedmethodoccuredanexception",e);}finally{if(redisClient.get(key,String.class).equals(value)){redisClient.tryRelease(key);}}returnnull;}}

这样子的话,在方法中只要以如下的方式,就可以对于方法中传入的参数加锁了

@RedisDistributedLock(key="#param",keepMills=2*10*1000)publicvoidmethod(Stringparam){//业务代码}
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
多特后防四大天王是哪些 iPhoe4还原所有设置后变成语音朗读而且滑屏无法正常使用 金鱼在鱼缸里几天能不会死掉? 金鱼放鱼缸多久合适 金鱼放鱼缸的时间 想学会缅甸语请问大神昆明附近有没有好一点的班? 昆明哪里可以学缅甸语?哪里不贵,哪里有优势? 昆明哪里可以学缅甸语啊?想去学几个月。 要出去缅甸出差一段时间,请问下昆明什么地方有好的缅甸语培训?? 叔叔要去缅甸做生意,帮他找间学校学缅甸语,简单的就行了,只有两个月... 昆明学缅甸语哪里学呀??? 想学几个月,再去一次缅甸。 安室透喜欢的女生是谁 基于注解的redis分布式锁的简单实现 贴对联怎么贴是正确的 微信视频号的直播预告可以修改时间吗? 煮姜枣茶的正确做法 夏季姜枣茶的配方和制作方法 72岁老人血糖八点几每天早上吃小米,黑豆,黑米,薏仁,核桃,鸭蛋等这样打... 西伯利亚是中国的领土 华为手机字体如何设置简体中文繁体中文? 洛阳的二甲医院有哪些医院 洛阳市二甲医院有哪些 洛阳二级医院有哪些 结石到输尿管有什么症状 结石在输尿管末端症状 结石到输尿管末端症状 肾结石掉到输尿管末端怎么办 尿结石快掉出来的症状 肾结石到输尿管末端怎么办 输尿管结石快出来的症状 结石到输尿管末端怎么办 赤壁悬流风景区能玩几小时,太行赤壁悬流景区门票 赤壁悬流景区是哪条河_平顺至潞城的公交车 光芒剧情介绍光芒主要演员介绍 肋间隙可有压痛的检查 MacBook pro有没有能装的免费的杀毒软件?担心中毒了咋办啊? 胸椎错位怎么检测 选购宝宝奶粉盒的必要性有哪些? 胸椎小关节错位有什么症状 进口奶粉分装什么意思 胸摊关节紊乱的临床表现 胸椎病常见的临床表现 江淮骏铃更换空调滤芯的步骤是什么? 苹果手机如何将照片导入电脑? 什么叫净资 小红书关注界面变成竖屏是什么原因? 别讨论了,李约瑟难题的标准答案来了!看看和你的答案一样吗 精神堡垒设计 教材中增加英雄故事的意义是什么? 怎样判断刹车盘该换了? 英语翻译 你看见我的包了吗?