【Redisson】基于自定義注解的Redisson分布式鎖實現

前言

在項目中,經常需要使用Redisson分布式鎖來保證并發操作的安全性。在未引入基于注解的分布式鎖之前,我們需要手動編寫獲取鎖、判斷鎖、釋放鎖的邏輯,導致代碼重復且冗長。為了簡化這一過程,我們引入了基于注解的分布式鎖,通過一個注解就可以實現獲取鎖、判斷鎖、處理完成后釋放鎖的邏輯。這樣可以大大簡化代碼,提高開發效率。

目標

使用@DistributedLock即可實現獲取鎖,判斷鎖,處理完成后釋放鎖的邏輯。

@RestController
public class HelloController {@DistributedLock@GetMapping("/helloWorld")public void helloWorld() throws InterruptedException {System.out.println("helloWorld");Thread.sleep(100000);}
}

涉及知識

  • SpringBoot
  • Spring AOP
  • Redisson
  • 自定義注解
  • 統一異常處理
  • SpEL表達式

代碼實現

引入依賴

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.21.3</version>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>

注解類

/*** 分布式鎖注解* @author 只有影子*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {/*** 獲取鎖失敗時,默認的錯誤描述*/String errorDesc() default "任務正在處理中,請耐心等待";/*** SpEL表達式,用于獲取鎖的key* 示例:* "#name"則從方法參數中獲取name的值作為key* "#user.id"則從方法參數中獲取user對象中的id作為key*/String[] keys() default {};/*** key的前綴,為空時取類名+方法名*/String prefix() default "";
}

切面類

/*** 分布式鎖切面類* @author 只有影子*/
@Slf4j
@Aspect
@Component
public class DistributedLockAspect {@Resourceprivate RedissonClient redissonClient;private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();@Around("@annotation(distributedLock)")public Object around(ProceedingJoinPoint joinPoint,DistributedLock distributedLock) throws Throwable {String redisKey = getRedisKey(joinPoint, distributedLock);log.info("拼接后的redisKey為:" + redisKey);RLock lock = redissonClient.getLock(redisKey);if (!lock.tryLock()) {// 可以使用自己的異常類,演示用RuntimeExceptionthrow new RuntimeException(distributedLock.errorDesc());}// 執行被切面的方法try {return joinPoint.proceed();} finally {lock.unlock();}}/*** 動態解密參數,拼接redisKey* @param joinPoint* @param distributedLock  注解* @return*/private String getRedisKey(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();EvaluationContext context = new MethodBasedEvaluationContext(TypedValue.NULL, method, joinPoint.getArgs(), PARAMETER_NAME_DISCOVERER);StringBuilder redisKey = new StringBuilder();// 拼接redis前綴if (StringUtil.isNotBlank(distributedLock.prefix())) {redisKey.append(distributedLock.prefix()).append(":");} else {// 獲取類名String className = joinPoint.getTarget().getClass().getSimpleName();// 獲取方法名String methodName = joinPoint.getSignature().getName();redisKey.append(className).append(":").append(methodName).append(":");}ExpressionParser parser = new SpelExpressionParser();for (String key : distributedLock.keys()) {// keys是個SpEL表達式Expression expression = parser.parseExpression(key);Object value = expression.getValue(context);redisKey.append(ObjectUtils.nullSafeToString(value));}return redisKey.toString();}
}

統一異常處理類

/*** 全局異常處理類* @author 只有影子*/
@RestControllerAdvice
public class ExceptionHandle {@ExceptionHandler(Exception.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public String sendErrorResponseSystem(Exception e) {// 這里只是模擬返回值,實際項目中一般都是返回封裝好的統一返回類return e.getMessage();}
}

還需要將redis配置讀入,這里就不體現

使用示例

1. 無參方法或者需要加方法級的鎖

@DistributedLock
@GetMapping("/helloWorld")
public void helloWorld() throws InterruptedException {System.out.println("helloWorld");Thread.sleep(100000);
}

調用接口:http://localhost:8080/helloWorld

拼接后的redisKey為:HelloController:helloWorld:

可以看到,無參方法的key為HelloController:helloWorld:,其中HelloController為類名,helloWorld為方法名,因為是無參方法,所以沒有接下來的參數。

這時候,再次調用改接口,則不會再進去接口,會被切面類直接攔截,返回如下結果:

image-20231123222250866

在實際生產使用中,這種情況一般被用來在自動任務上標注,因為在集群環境中自動任務同一時間一般只需要啟動一個。

2. 有參數方法,其中key從name中取值

@DistributedLock(keys = "#name")
@GetMapping("/hello1")
public String hello1(String name) throws InterruptedException {String s = "hello " + name;System.out.println(s);Thread.sleep(100000);return s;
}

調用接口為:http://localhost:8080/hello1?name=hurry

拼接后的redisKey為:HelloController:hello1:hurry

這時候,再通過hurry這個名稱調用時,就不會再處理,而name換為zhangsan時,則就能正常進入接口。

這時候redis中的key為

> 127.0.0.1@6379 connected!
> keys *
HelloController:hello2:zhangsan
HelloController:hello2:hurry

實際業務中,需要根據不同的參數值進行加鎖的場景。

3. 有參數方法,其中key需要從user對象中獲取name

@DistributedLock(keys = "#user.name")
@GetMapping("/hello2")
public String hello2(User user) throws InterruptedException {String s = "hello " + user.getName();System.out.println(s);Thread.sleep(100000);return s;
}

需要從某個對象中獲取指定屬性作為key的場景

4.有參數方法,其中key從name上取值并指定前綴

@DistributedLock(keys = "#name",prefix = "testPrefix")
@GetMapping("/hello3")
public String hello3(String name) throws InterruptedException {String s = "hello " + name;System.out.println(s);Thread.sleep(100000);return s;
}

需要指定key前綴的場景

最后

由于文章篇幅原因,很多東西沒有深入的講解,但是基于以上代碼基本實現了基于注解的分布式鎖,可以大大提到開發效率。如果還有其他需要拓展的功能,可以通過在注解類增加屬性及在切面類中通過不同的屬性進行不同的處理來實現。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/164522.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/164522.shtml
英文地址,請注明出處:http://en.pswp.cn/news/164522.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

JS獲取時間戳的五種方法

一、JavasCRIPT時間轉時間戳 JavaScript獲得時間戳的方法有五種&#xff0c;后四種都是通過實例化時間對象new Date() 來進一步獲取當前的時間戳&#xff0c;JavaScript處理時間主要使用時間對象Date。 方法一&#xff1a;Date.now() Date.now()可以獲得當前的時間戳&#x…

思維模型 等待效應

本系列文章 主要是 分享 思維模型 &#xff0c;涉及各個領域&#xff0c;重在提升認知。越是等待&#xff0c;越是焦慮。 1 等待效應的應用 1.1 等待效應在管理中的應用 西南航空公司是一家美國的航空公司&#xff0c;它在管理中運用了等待效應。西南航空公司鼓勵員工在工作中…

快速學會使用Python3.12的新特性

一、 PEP 695: 類型形參語法的革新 PEP 695 在 Python 3.12 中引入了一種新穎且更為清晰的方式來定義泛型類和函數&#xff0c;旨在提升類型參數的明確性和簡潔性。這個提案不僅改善了類型系統的可讀性&#xff0c;還增強了其功能性。以下是這些變化的詳細概述&#xff1a; 1…

(四)C語言之符號常量概述

&#xff08;四&#xff09;C語言之符號常量概述 一、符號常量概述 一、符號常量概述 在程序中使用像300,20等這樣的等類似的“幻數”不是一個好的習慣&#xff0c;它們無法向閱讀該程序的人提供更多有用的信息&#xff0c;從而使得修改程序變得困難。處理這種幻數的一種方法是…

unreal 指定windows SDK

路徑 &#xff1a; “C:\Users\Administrator\AppData\Roaming\Unreal Engine\UnrealBuildTool\BuildConfiguration.xml” 在Configuration中添加 <WindowsPlatform><WindowsSdkVersion>10.0.20348.0</WindowsSdkVersion></WindowsPlatform>示例&…

R數據分析:集成學習方法之隨機生存森林的原理和做法,實例解析

很久很久以前給大家寫過決策樹&#xff0c;非常簡單明了的算法。今天給大家寫隨機&#xff08;生存&#xff09;森林&#xff0c;隨機森林是集成了很多個決策數的集成模型。像隨機森林這樣將很多個基本學習器集合起來形成一個更加強大的學習器的這么一種集成思想還是非常好的。…

算法面試題:反轉一個整數

題目&#xff1a;反轉一個整數。例如&#xff0c;輸入123&#xff0c;輸出321&#xff1b;輸入-456&#xff0c;輸出-654。注意&#xff1a;反轉后的整數在32位帶符號整數范圍內。 編寫一個函數 reverseInteger(x: int) -> int 來實現這個功能。 答案&#xff1a; def re…

【前端】必學知識ES6 1小時學會

1.ES6概述 2.let和const的認識 3.let、const、var的區別 4.模板字符串 5.函數默認參數 6.箭頭函數【重點】 ?編輯7.對象初始化簡寫以及案例分析 【重點】 8.對象解構 8.對象傳播操作符 9.對象傳播操作符案例分析 ?編輯 10.數組Map 11.數組Reduce 12.NodeJS小結 …

代碼隨想錄算法訓練營第四十四天【動態規劃part06】 | 完全背包、518. 零錢兌換 II、377. 組合總和 Ⅳ

完全背包 有N件物品和一個最多能背重量為W的背包。第i件物品的重量是weight[i]&#xff0c;得到的價值是value[i] 。每件物品都有無限個&#xff08;也就是可以放入背包多次&#xff09;&#xff0c;求解將哪些物品裝入背包里物品價值總和最大。 題目鏈接&#xff1a; 題目頁…

計算機畢業設計 基于Hadoop的物品租賃系統的設計與實現 Java實戰項目 附源碼+文檔+視頻講解

博主介紹&#xff1a;?從事軟件開發10年之余&#xff0c;專注于Java技術領域、Python人工智能及數據挖掘、小程序項目開發和Android項目開發等。CSDN、掘金、華為云、InfoQ、阿里云等平臺優質作者? &#x1f345;文末獲取源碼聯系&#x1f345; &#x1f447;&#x1f3fb; 精…

YOLO目標檢測——泄露檢測數據集下載分享【含對應voc、coco和yolo三種格式標簽】

實際項目應用&#xff1a;泄露檢測數據集說明&#xff1a;泄露檢測數據集&#xff0c;真實場景的高質量圖片數據&#xff0c;數據場景豐富&#xff0c;含多個類別標簽說明&#xff1a;使用lableimg標注軟件標注&#xff0c;標注框質量高&#xff0c;含voc(xml)、coco(json)和yo…

AES 加解密

AES 加解密 AES(Advanced Encryption Standard),又稱高級加密標準,是一種對稱加密算法,也是目前廣泛使用的加密技術之一。其主要特點是加密速度快、安全性高、可擴展性好等。 AES 算法采用對稱加密的方式,即加密和解密使用相同的密鑰進行操作。密鑰長度可以是 128、192…

【JavaSE】不允許你不會使用String類

&#x1f3a5; 個人主頁&#xff1a;深魚~&#x1f525;收錄專欄&#xff1a;JavaSE&#x1f304;歡迎 &#x1f44d;點贊?評論?收藏 目錄 前言&#xff1a; 一、常用方法 1.1 字符串構造 1.2 String對象的比較 &#xff08;1&#xff09;比較是否引用同一個對象 注意…

從零開始的C++(十九)

紅黑樹&#xff1a; 一種接近平衡的二叉樹&#xff0c;平衡程度低于搜索二叉樹。 特點&#xff1a; 1.根節點為黑 2.黑色結點的子結點可以是紅色結點或黑色結點。 3.紅色結點的子結點只能是黑色結點。 4.每個結點到其所有葉子結點的路徑的黑色結點個數相同。 5.指向空的…

OmniGraffle

安裝 在mac上安裝OmniGraffle&#xff0c;找一個正版或者啥的都行&#xff0c;安裝好后&#xff0c;可以直接在網上找一個激活碼&#xff0c;然后找到軟件的許可證&#xff0c;進行添加即可。 使用 新建空白頁 然后圖形啥的看一眼工具欄就知道了&#xff0c;顏色形狀還是挺…

音視頻項目—基于FFmpeg和SDL的音視頻播放器解析(二十一)

介紹 在本系列&#xff0c;我打算花大篇幅講解我的 gitee 項目音視頻播放器&#xff0c;在這個項目&#xff0c;您可以學到音視頻解封裝&#xff0c;解碼&#xff0c;SDL渲染相關的知識。您對源代碼感興趣的話&#xff0c;請查看基于FFmpeg和SDL的音視頻播放器 如果您不理解本…

【C++】拷貝構造函數,析構函數詳解!

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;個人主頁 &#xff1a;阿然成長日記 …

【LeetCode】挑戰100天 Day13(熱題+面試經典150題)

【LeetCode】挑戰100天 Day13&#xff08;熱題面試經典150題&#xff09; 一、LeetCode介紹二、LeetCode 熱題 HOT 100-152.1 題目2.2 題解 三、面試經典 150 題-153.1 題目3.2 題解 一、LeetCode介紹 LeetCode是一個在線編程網站&#xff0c;提供各種算法和數據結構的題目&…

Vue3 實現elementPlus的table列寬調整和拖拽

1、需要的包 // 除了Vue和element-plus外還需要以下的包 npm install sortablejs2、具體代碼如下&#xff0c;可直接粘貼運行 <template><div class"draggable-table"><el-table ref"tableRef":data"tableData.data":key"…

Java-飛翔的小鳥

前言 基于Java的飛翔小鳥游戲&#xff0c;本代碼來自b站up主分享。本游戲所需的圖片素材需要自己獲取并下載&#xff0c;在此視頻下&#xff0c;視頻鏈接&#xff1a;【Java經典小游戲項目之飛翔的小鳥】 https://www.bilibili.com/video/BV1ou411o7br/?p10&share_source…