# lock-spring-boot-starter **Repository Path**: yzey/lock-spring-boot-starter ## Basic Information - **Project Name**: lock-spring-boot-starter - **Description**: No description available - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 3 - **Created**: 2021-07-02 - **Last Updated**: 2021-07-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # lock-spring-boot-starter > 分布式锁客户端starter ## 介绍 ### 说明 工程接口(扩展点): ​ 接口->com.javacoo.lock.client.api.Lock 基于xkernel 提供的SPI机制,结合SpringBoot注解 ConditionalOnBean,ConditionalOnProperty实现。 ### 类关系图 ![输入图片说明](https://images.gitee.com/uploads/images/2021/0522/230000_c90b9ef3_121703.png "Lock.png") ### 项目结构 ```java lock-spring-boot-starter └── src ├── main │ ├── java │ │ └── com.javacoo │ │ ├────── lock │ │ │ ├──────client │ │ │ │ ├── api │ │ │ │ │ ├── annotation │ │ │ │ │ │ └── MethodLock 锁注解 │ │ │ │ │ ├── client │ │ │ │ │ │ └── Lock 锁接口 │ │ │ │ │ ├── aspect │ │ │ │ │ │ └── LockAspect 锁切面 │ │ │ │ │ ├── config │ │ │ │ │ │ └── LockConfig 锁配置 │ │ │ │ │ ├── exception │ │ │ │ │ └── LockException 锁异常 │ │ │ │ └── internal 接口内部实现 │ │ │ │ ├── redis │ │ │ │ ├── RedissionConfig Redission配置类 │ │ │ │ └── RedssionLock 锁接口实现类 │ │ │ └──────starter │ │ │ ├── LockAutoConfiguration 自动配置类 │ │ │ └── LockHolder 锁接口对象持有者 │ └── resource │ ├── META-INF │ └── ext │ └── internal │ └── com.javacoo.lock.client.api.Lock └── test 测试 ``` # 如何使用 1. pom依赖: ```xml com.javacoo lock-spring-boot-starter 1.0.0 ``` 2. 配置参数,如果使用默认实现,则无需配置,如要扩展则需要,配置如下: ```properties #lock是否可用,默认可用 lock.enabled = true #lock实现,默认内部实现 lock.impl = default ``` 3. 方法加注解,如: ```java @MethodLock(fieldName = "applNo") @Override public BaseResponse gjjloanConfirm(LoanConfirmRequest request) ``` # SPI扩展 基于xkernel 提供的SPI机制,扩展非常方便,大致步骤如下: 1. 实现锁接口:如 com.xxxx.xxxx.MyLockImpl 2. 配置锁接口: - 在项目resource目录新建包->META-INF->services - 创建com.javacoo.lock.client.api.Lock文件,文件内容:实现类的全局限定名,如: ```properties myLock=com.xxxx.xxxx.MyLockImpl ``` - 修改配置文件,添加如下内容: ```properties #lock实现 lock.impl = myLock ``` ## 默认实现 1、锁接口: ```java /** * 锁接口 *
  • * * @author: duanyong * @since: 2020/6/22 10:19 */ @Spi(LockConfig.DEFAULT_IMPL) public interface Lock { /**超时时间*/ int TIMEOUT_SECOND = 60; /** * 对lockKey加锁 *
  • * @author duanyong * @date 2020/6/22 10:30 * @param lockKey:lockKey * @return: T 锁对象 */ T lock(String lockKey); /** * 对lockKey加锁,timeout后过期 *
  • * @author duanyong * @date 2020/6/22 10:31 * @param lockKey: lockKey * @param timeout: 锁超时时间,单位:秒 * @return: T 锁对象 */ T lock(String lockKey, int timeout); /** * 对lockKey加锁,指定时间单位,timeout后过期 *
  • * @author duanyong * @date 2020/6/22 10:33 * @param lockKey: lockKey * @param unit: 时间单位 * @param timeout: 锁超时时间 * @return: T 锁对象 */ T lock(String lockKey, TimeUnit unit , int timeout); /** * 尝试获取锁 *
  • * @author duanyong * @date 2020/6/22 10:35 * @param lockKey: lockKey * @return: boolean 是否成功,成功返回:true */ boolean tryLock(String lockKey); /** * 尝试获取锁 *
  • * @author duanyong * @date 2020/6/22 10:35 * @param lockKey: lockKey * @param waitTime:最多等待时间 * @param timeout:上锁后自动释放锁时间 * @return: boolean 是否成功,成功返回:true */ boolean tryLock(String lockKey, int waitTime, int timeout); /** * 尝试获取锁 *
  • * @author duanyong * @date 2020/6/22 10:36 * @param lockKey: lockKey * @param unit:时间单位 * @param waitTime:最多等待时间 * @param timeout:上锁后自动释放锁时间 * @return: boolean 是否成功,成功返回:true */ boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int timeout); /** * 释放锁 *
  • * @author duanyong * @date 2020/6/22 10:37 * @param lockKey: lockKey * @return: void */ void unlock(String lockKey); /** * 释放锁 *
  • * @author duanyong * @date 2020/6/22 10:37 * @param lock:锁对象 * @return: void */ void unlock(T lock); } ``` 2、实现锁接口,如默认实现: ```java /** * 锁接口实现类 *
  • 基于Redssion
  • * * @author: duanyong * @since: 2020/6/22 10:39 */ @Slf4j public class RedssionLock implements Lock { /**约定缓存前缀:appId:模块:key*/ public static final String CACHE_PREFIX_KEY ="javacoo:service:lock:"; @Autowired private RedissonClient redissonClient; /** * 对lockKey加锁 *
  • 阻塞获取锁
  • * * @param lockKey :lockKey * @author duanyong * @date 2020/6/22 10:30 * @return: T 锁对象 */ @Override public RLock lock(String lockKey) { log.info(">>>>> lock key[{}]",lockKey); Assert.hasText(lockKey,"lockKey 不能为空"); RLock lock = redissonClient.getLock(CACHE_PREFIX_KEY +lockKey); lock.lock(); return lock; } /** * 对lockKey加锁,timeout后过期 *
  • * * @param lockKey : lockKey * @param timeout : 锁超时时间,单位:秒 * @author duanyong * @date 2020/6/22 10:31 * @return: T 锁对象 */ @Override public RLock lock(String lockKey, int timeout) { return lock(lockKey, TimeUnit.SECONDS,timeout); } /** * 对lockKey加锁,指定时间单位,timeout后过期 *
  • * * @param lockKey : lockKey * @param unit : 时间单位 * @param timeout : 锁超时时间 * @author duanyong * @date 2020/6/22 10:33 * @return: T 锁对象 */ @Override public RLock lock(String lockKey, TimeUnit unit, int timeout) { log.info(">>>>> lockKey:{},TimeUnit:{},timeout:{}",lockKey,unit,timeout); Assert.hasText(lockKey,"lockKey 不能为空"); RLock lock = redissonClient.getLock(CACHE_PREFIX_KEY +lockKey); lock.lock(timeout, unit); return lock; } /** * 尝试获取锁 *
  • 获取锁失败立即返回,上锁后60秒自动释放锁
  • * * @param lockKey : lockKey * @author duanyong * @date 2020/6/22 10:35 * @return: boolean 是否成功,成功返回:true */ @Override public boolean tryLock(String lockKey) { return tryLock(lockKey,0,60); } /** * 尝试获取锁 *
  • * * @param lockKey : lockKey * @param waitTime :最多等待时间 * @param timeout :上锁后自动释放锁时间 * @author duanyong * @date 2020/6/22 10:35 * @return: boolean 是否成功,成功返回:true */ @Override public boolean tryLock(String lockKey, int waitTime, int timeout) { return tryLock(lockKey, TimeUnit.SECONDS,waitTime,timeout); } /** * 尝试获取锁 *
  • * * @param lockKey : lockKey * @param unit :时间单位 * @param waitTime :最多等待时间 * @param timeout :上锁后自动释放锁时间 * @author duanyong * @date 2020/6/22 10:36 * @return: boolean 是否成功,成功返回:true */ @Override public boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int timeout) { Assert.hasText(lockKey,"lockKey 不能为空"); lockKey = CACHE_PREFIX_KEY +lockKey; log.info(">>>>> tryLock lockKey:{},TimeUnit:{},waitTime:{},timeout:{}",lockKey,unit,waitTime,timeout); RLock lock = redissonClient.getLock(lockKey); try { return lock.tryLock(waitTime, timeout, unit); } catch (Exception e) { e.printStackTrace(); log.error("尝试获取锁:{},TimeUnit:{},waitTime:{},timeout:{},失败",lockKey,unit,waitTime,timeout,e); } return false; } /** * 释放锁 *
  • * * @param lockKey : lockKey * @author duanyong * @date 2020/6/22 10:37 * @return: void */ @Override public void unlock(String lockKey) { lockKey = CACHE_PREFIX_KEY +lockKey; try { RLock lock = redissonClient.getLock(lockKey); if(!lock.isLocked()){ log.error(">>>>> unlock lockKey fail:{},isLocked:{}",lockKey,lock.isLocked()); return; } if(!lock.isHeldByCurrentThread()){ log.error(">>>>> unlock lockKey fail:{},isHeldByCurrentThread:{}",lockKey,lock.isHeldByCurrentThread()); return; } lock.unlock(); log.info(">>>>> unlock lockKey success:{}",lockKey); } catch (Exception e) { e.printStackTrace(); log.error("释放锁失败:{}",lockKey,e); } } /** * 释放锁 *
  • * * @param lock :锁对象 * @author duanyong * @date 2020/6/22 10:37 * @return: void */ @Override public void unlock(RLock lock) { lock.unlock(); } ``` 3、锁注解 ```java /** * 服务锁注解 *
  • * @author duanyong * @date 2020/10/14 9:20 */ @Documented @Inherited @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MethodLock { /** * 指定需要加入锁的字段 *
  • * @author duanyong * @date 2020/10/14 9:21 * @return: java.lang.String[] */ String[] fieldName() default {}; /** * 锁的有效时间,单位为秒,默认值为60 *
  • * @author duanyong * @date 2020/10/14 9:21 * @return: int */ int timeInSecond() default 60; /** * 指定字段在参数列表中的索引 *
  • * @author duanyong * @date 2020/10/14 9:22 * @return: int */ int paramIndex() default 0; } ``` 4、锁切面 ```java /** * 业务拦截器 *
  • * @author duanyong * @date 2021/3/1 15:56 */ @Slf4j @Aspect @Component public class LockAspect { /**锁对象*/ @Autowired private Lock lock; @Around("@annotation(methodLock)") public Object around(ProceedingJoinPoint joinPoint, MethodLock methodLock) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); // 获取被拦截的方法 Method method = signature.getMethod(); // 获取被拦截的类名 String className = signature.getDeclaringType().getSimpleName(); // 获取被拦截的方法名 String methodName = method.getName(); log.info("[加锁交易]请求开始,方法口名:{}", className+"."+methodName); //生成锁KEY String lockKey = generateKey(joinPoint.getArgs(),className,methodName,methodLock); if(tryLock(lockKey,methodLock)){ try { log.info("[加锁交易]加锁成功,MethodName:{},key:{}",methodName,lockKey); return joinPoint.proceed(); } catch (Exception e) { log.error("[加锁交易]执行方法发生异常,MethodName:{},lockKey:{},Exception:{}",methodName,lockKey,e); throw e; }finally { lock.unlock(lockKey); log.info("[加锁交易]方法解锁,MethodName:{},key:{}",methodName,lockKey); } }else{ log.error("[加锁交易]过滤频繁操作,加锁失败,Key:{}",lockKey); } return null; } /** * 对添加RedisLock注解的方法进行重复访问限制 * @param cacheKey * @param methodLock */ private boolean tryLock(String cacheKey,MethodLock methodLock) { boolean isLocked = lock.tryLock(cacheKey, TimeUnit.SECONDS,0,methodLock.timeInSecond()); if(isLocked){ log.info("[交易系统拦截器]加锁成功,KEY:{}",cacheKey); } return isLocked; } /** * 生成锁的key key=类名-方法名-参数集合 *
  • * @author duanyong * @date 2021/4/29 13:08 * @param args: 参数 * @param className: 类名 * @param methodName: 方法名 * @param methodLock: 锁接口 * @return: java.lang.String 锁的key */ private String generateKey(Object[] args, String className, String methodName,MethodLock methodLock) throws Exception { //根据参数列表索引获取入参 默认为第一个参数 Object param = args[methodLock.paramIndex()]; List fieldValueList = new ArrayList<>(); String[] fieldNames = methodLock.fieldName(); if(param instanceof String){ fieldValueList.add(String.valueOf(param)); }else if(param instanceof Long){ fieldValueList.add(String.valueOf(param)); }else{ String jsonString = JSON.toJSONString(param); JSONObject jsonObject = JSON.parseObject(jsonString); for (String filedName:fieldNames){ if(jsonObject.containsKey(filedName)){ fieldValueList.add(jsonObject.getString(filedName)); } } } StringBuilder keyBuilder = new StringBuilder(); keyBuilder.append(className) .append("-") .append(methodName) .append("-") .append(fieldValueList); return keyBuilder.toString(); } } ``` 5、锁接口对象持有者 ```java /** * 锁接口对象持有者 *
  • * * @author: duanyong * @since: 2021/3/16 8:56 */ public class LockHolder { /** 锁对象*/ static Lock lock; public static Optional getLock() { return Optional.ofNullable(lock); } } ``` 6、自动配置。 ```java /** * 自动配置类 *
  • * @author duanyong * @date 2021/3/5 9:50 */ @Slf4j @Configuration @EnableConfigurationProperties(value = LockConfig.class) @ConditionalOnClass(Lock.class) @ConditionalOnProperty(prefix = LockConfig.PREFIX, value = LockConfig.ENABLED, matchIfMissing = true) public class LockAutoConfiguration { @Autowired private LockConfig lockConfig; @Bean @ConditionalOnMissingBean(Lock.class) public Lock createLock() { log.info("初始化分布式锁,实现类名称:{}",lockConfig.getImpl()); LockHolder.lock = ExtensionLoader.getExtensionLoader(Lock.class).getExtension(lockConfig.getImpl()); log.info("初始化分布式锁成功,实现类:{}",LockHolder.lock); return LockHolder.lock; } @Bean public LockAspect createLockAspect() { return new LockAspect(); } } ```