Springboot-Starter造輪子之自動鎖組件lock-starter實現(xiàn)
前言
可能有人會有疑問,為什么外面已經(jīng)有更好的組件,為什么還要重復的造輪子,只能說,別人的永遠是別人的,自己不去造一下,就只能知其然,而不知其所以然。(其實就為了卷)
在日常業(yè)務(wù)開發(fā)的過程中,我們經(jīng)常會遇到存在高并發(fā)的場景,這個時候都會選擇使用redis
來實現(xiàn)一個鎖,來防止并發(fā)。
但是很多時候,我們可能業(yè)務(wù)完成后,就需要把鎖釋放掉,給下一個線程用,但是如果我們忘記了釋放鎖,可能就會存在死鎖的問題。(對于使用鎖不太熟練的話,這種情況時常發(fā)生,雖然很多時候,我們的鎖是有過期時間的,但是如果忘記了釋放,那么在這個過期時間內(nèi),還是會存在大的損失)。
還有一點就是,在我們使用redis實現(xiàn)一個鎖的時候,我們需要導入redisClient,設(shè)置key,設(shè)置過期時間,設(shè)置是否鎖等等一些重復的操作。前面的哪些步驟,很多都是重復的,所以我們可以想一個方法,來把重復的東西都抽象出來,做成統(tǒng)一的處理,同時哪些變化的值,提供一個設(shè)置的入口。
抽出來的東西,我們還可以封裝成一個spring-boot-stater,這樣我們只需要寫一份,就可以在不同的項目中使用了。 說干就干,下面我們使用redisson,完成一個自動鎖的starter
。
實現(xiàn)
首先,我們分析一下哪些東西是我們需要進行合并,哪些又是需要提供給使用方的。得到下面的一些問題
- 加鎖、釋放鎖過程 我們需要合并起來
- 鎖key,加鎖時間......這些需要給使用方注入
- 鎖的key該怎么去生成(很多時候,我們需要根據(jù)業(yè)務(wù)字段去構(gòu)造一個key,比如 user:{userId}),那么這個userId該怎么獲取?
我們從上面需要解決的問題,去思考需要怎么去實現(xiàn)。我們需要封裝一些公共的邏輯,又需要提供一些配置的入庫,這樣的話,我們可以嘗試一種方法,使用 注解+AOP
,通過注解的方式完成加鎖、解鎖。(很多時候,如果需要抽出一些公共的方法,會用到注解+AOP
去實現(xiàn))
定義注解
AutoLock 注解
一個鎖需要有的信息有,key,加鎖的時間,時間單位,是否嘗試加鎖,加鎖等待時間 等等。(如果還有其他的業(yè)務(wù)需要,可以添加一個擴展內(nèi)容,自己去解析處理) 那么這個注解的屬性就可以知道有哪些了
/** * 鎖的基本信息 */ @Target({ElementType.METHOD}) @Documented @Retention(RetentionPolicy.RUNTIME) public @interface AutoLock { /** * 鎖前綴 */ String prefix() default "anoxia:lock"; /** * 加鎖時間 */ long lockTime() default 30; /** * 是否嘗試加鎖 */ boolean tryLock() default true; /** * 等待時間,-1 不等待 */ long waitTime() default -1; /** * 鎖時間類型 */ TimeUnit timeUnit() default TimeUnit.MILLISECONDS; }
LockField 注解
這個注解添加到參數(shù)屬性上面,用來解決上面提到獲取不同的業(yè)務(wù)參數(shù)內(nèi)容構(gòu)造key的問題。所以我們需要提供一個獲取哪些字段來構(gòu)造這個key配置,這里需要考慮兩個問題:
- 1、參數(shù)是基本類型
- 2、參數(shù)是引用類型 - 這種類型需要從對象中拿到對象的屬性值
/** * 構(gòu)建鎖的業(yè)務(wù)數(shù)據(jù) * @author huangle * @date 2023/5/5 15:01 */ @Target({ElementType.PARAMETER}) @Documented @Retention(RetentionPolicy.RUNTIME) public @interface LockField { String[] fieldNames() default {}; }
定義切面
重點就在這個切面里面,我們需要在這里完成key的合成,鎖的獲取與釋放。整個過程可以分為以下幾步
- 獲取鎖的基本信息,構(gòu)建key
- 加鎖,執(zhí)行業(yè)務(wù)
- 業(yè)務(wù)完成,釋放鎖
/** * 自動鎖切面 * 處理加鎖解鎖邏輯 * * @author huangle * @date 2023/5/5 14:50 */ @Aspect @Component public class AutoLockAspect { private final static Logger LOGGER = LoggerFactory.getLogger(AutoLockAspect.class); @Resource private RedissonClient redissonClient; private static final String REDIS_LOCK_PREFIX = "anoxiaLock"; private static final String SEPARATOR = ":"; /** * 定義切點 */ @Pointcut("@annotation(cn.anoxia.lock.annotation.AutoLock)") public void lockPoincut() { } /** * 定義攔截處理方式 * * @return */ @Around("lockPoincut()") public Object doLock(ProceedingJoinPoint joinPoint) throws Throwable { // 獲取需要加鎖的方法 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Method method = methodSignature.getMethod(); // 獲取鎖注解 AutoLock autoLock = method.getAnnotation(AutoLock.class); // 獲取鎖前綴 String prefix = autoLock.prefix(); // 獲取方法參數(shù) Parameter[] parameters = method.getParameters(); StringBuilder lockKeyStr = new StringBuilder(prefix); Object[] args = joinPoint.getArgs(); // 遍歷參數(shù) int index = -1; LockField lockField; // 構(gòu)建key for (Parameter parameter : parameters) { Object arg = args[++index]; lockField = parameter.getAnnotation(LockField.class); if (lockField == null) { continue; } String[] fieldNames = lockField.fieldNames(); if (fieldNames == null || fieldNames.length == 0) { lockKeyStr.append(SEPARATOR).append(arg); } else { List<Object> filedValues = ReflectionUtil.getFiledValues(parameter.getType(), arg, fieldNames); for (Object value : filedValues) { lockKeyStr.append(SEPARATOR).append(value); } } } String lockKey = REDIS_LOCK_PREFIX + SEPARATOR + lockKeyStr; RLock lock = redissonClient.getLock(lockKey); // 加鎖標志位 boolean lockFlag = false; try { long lockTime = autoLock.lockTime(); long waitTime = autoLock.waitTime(); TimeUnit timeUnit = autoLock.timeUnit(); boolean tryLock = autoLock.tryLock(); try { if (tryLock) { lockFlag = lock.tryLock(waitTime, lockTime, timeUnit); } else { lock.lock(lockTime, timeUnit); lockFlag = true; } }catch (Exception e){ LOGGER.error("加鎖失?。?,錯誤信息", e); throw new RuntimeException("加鎖失?。?); } if (!lockFlag) { throw new RuntimeException("加鎖失??!"); } // 執(zhí)行業(yè)務(wù) return joinPoint.proceed(); } finally { // 釋放鎖 if (lockFlag) { lock.unlock(); LOGGER.info("釋放鎖完成,key:{}",lockKey); } } } }
獲取業(yè)務(wù)屬性
這個是一個獲取對象中字段的工具類,在一些常用的工具類里面也有實現(xiàn),可以直接使用也可以自己實現(xiàn)一個
/** * @author huangle * @date 2023/5/5 15:17 */ public class ReflectionUtil { public static List<Object> getFiledValues(Class<?> type, Object target, String[] fieldNames) throws IllegalAccessException { List<Field> fields = getFields(type, fieldNames); List<Object> valueList = new ArrayList(); Iterator fieldIterator = fields.iterator(); while(fieldIterator.hasNext()) { Field field = (Field)fieldIterator.next(); if (!field.isAccessible()) { field.setAccessible(true); } Object value = field.get(target); valueList.add(value); } return valueList; } public static List<Field> getFields(Class<?> claszz, String[] fieldNames) { if (fieldNames != null && fieldNames.length != 0) { List<String> needFieldList = Arrays.asList(fieldNames); List<Field> matchFieldList = new ArrayList(); List<Field> fields = getAllField(claszz); Iterator fieldIterator = fields.iterator(); while(fieldIterator.hasNext()) { Field field = (Field)fieldIterator.next(); if (needFieldList.contains(field.getName())) { matchFieldList.add(field); } } return matchFieldList; } else { return Collections.EMPTY_LIST; } } public static List<Field> getAllField(Class<?> claszz) { if (claszz == null) { return Collections.EMPTY_LIST; } else { List<Field> list = new ArrayList(); do { Field[] array = claszz.getDeclaredFields(); list.addAll(Arrays.asList(array)); claszz = claszz.getSuperclass(); } while(claszz != null && claszz != Object.class); return list; } } }
配置自動注入
在我們使用 starter 的時候,都是通過這種方式,來告訴spring在加載的時候,完成這個bean的初始化。這個過程基本是定死的。 就是編寫配置類,如果通過springBoot的EnableAutoConfiguration
來完成注入。注入后,我們就可以直接去使用這個封裝好的鎖了。
/** * @author huangle * @date 2023/5/5 14:50 */ @Configuration public class LockAutoConfig { @Bean public AutoLockAspect autoLockAspect(){ return new AutoLockAspect(); } } // spring.factories 中內(nèi)容 org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.anoxia.lock.config.LockAutoConfig
測試
我們先打包這個sarter,然后導入到一個項目里面(打包導入的過程就不說了,自己去看一下就可以) 直接上測試類,下面執(zhí)行后可以看到鎖已經(jīng)完成了釋放。如果業(yè)務(wù)拋出異常導致中斷也不用擔心鎖不會釋放的問題,因為我們是在 finally 中釋放鎖的
/** * @author huangle * @date 2023/5/5 14:28 */ @RestController @RequestMapping("/v1/user") public class UserController { @AutoLock(lockTime = 3, timeUnit = TimeUnit.MINUTES) @GetMapping("/getUser") public String getUser(@RequestParam @LockField String name) { return "hello:"+name; } @PostMapping("/userInfo") @AutoLock(lockTime = 1, timeUnit = TimeUnit.MINUTES) public String userInfo(@RequestBody @LockField(fieldNames = {"id", "name"}) UserDto userDto){ return userDto.getId()+":"+userDto.getName(); } }
總結(jié)
很多時候,一些公共的業(yè)務(wù)邏輯都可以被抽象出來成為一個獨立的組件而存在,我們可以在日常開發(fā)過程中,不斷的去思考和尋找看哪些可以被抽象出來,哪些可以更加簡化一些。然后嘗試去抽象出一個組件出來,這樣的話不但可以鍛煉自己的能力,還可以得到一些很好用的工具,當然自己抽出的組件可以存在問題,但是慢慢的鍛煉下來,總會變的越來越好。 怎么說呢,嘗試去做,能不能做好再說,做不好就一次又一次的去做。
以上就是Springboot-Starter造輪子之自動鎖組件(lock-starter)的詳細內(nèi)容,更多關(guān)于Springboot-Starter自動鎖組件(lock-starter)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Springboot通過run啟動web應(yīng)用的方法
這篇文章主要介紹了Springboot通過run啟動web應(yīng)用的方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-03-03Java入門絆腳石之Override和Overload的區(qū)別詳解
重寫是子類對父類的允許訪問的方法的實現(xiàn)過程進行重新編寫, 返回值和形參都不能改變。即外殼不變,核心重寫!重寫的好處在于子類可以根據(jù)需要,定義特定于自己的行為。重載是在一個類里面,方法名字相同,而參數(shù)不同。返回類型可以相同也可以不同2021-10-10Mybatis-Plus使用ID_WORKER生成主鍵id重復的解決方法
本文主要介紹了Mybatis-Plus使用ID_WORKER生成主鍵id重復的解決方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-07-07