Springboot-Starter造轮子之自动锁组件lock-starter实现

作者:Anoxia1 时间:2022-01-21 03:53:59 

前言

可能有人会有疑问,为什么外面已经有更好的组件,为什么还要重复的造轮子,只能说,别人的永远是别人的,自己不去造一下,就只能知其然,而不知其所以然。(其实就为了卷)

在日常业务开发的过程中,我们经常会遇到存在高并发的场景,这个时候都会选择使用redis来实现一个锁,来防止并发。

但是很多时候,我们可能业务完成后,就需要把锁释放掉,给下一个线程用,但是如果我们忘记了释放锁,可能就会存在死锁的问题。(对于使用锁不太熟练的话,这种情况时常发生,虽然很多时候,我们的锁是有过期时间的,但是如果忘记了释放,那么在这个过期时间内,还是会存在大的损失)。

还有一点就是,在我们使用redis实现一个锁的时候,我们需要导入redisClient,设置key,设置过期时间,设置是否锁等等一些重复的操作。前面的哪些步骤,很多都是重复的,所以我们可以想一个方法,来把重复的东西都抽象出来,做成统一的处理,同时哪些变化的值,提供一个设置的入口。

抽出来的东西,我们还可以封装成一个spring-boot-stater,这样我们只需要写一份,就可以在不同的项目中使用了。 说干就干,下面我们使用redisson,完成一个自动锁的starter

实现

首先,我们分析一下哪些东西是我们需要进行合并,哪些又是需要提供给使用方的。得到下面的一些问题

  • 加锁、释放锁过程 我们需要合并起来

  • 锁key,加锁时间......这些需要给使用方注入

  • 锁的key该怎么去生成(很多时候,我们需要根据业务字段去构造一个key,比如 user:{userId}),那么这个userId该怎么获取?

我们从上面需要解决的问题,去思考需要怎么去实现。我们需要封装一些公共的逻辑,又需要提供一些配置的入库,这样的话,我们可以尝试一种方法,使用 注解+AOP,通过注解的方式完成加锁、解锁。(很多时候,如果需要抽出一些公共的方法,会用到注解+AOP去实现)

定义注解

AutoLock 注解

一个锁需要有的信息有,key,加锁的时间,时间单位,是否尝试加锁,加锁等待时间 等等。(如果还有其他的业务需要,可以添加一个扩展内容,自己去解析处理) 那么这个注解的属性就可以知道有哪些了

/**
* 锁的基本信息
*/
@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 注解

这个注解添加到参数属性上面,用来解决上面提到获取不同的业务参数内容构造key的问题。所以我们需要提供一个获取哪些字段来构造这个key配置,这里需要考虑两个问题:

  • 1、参数是基本类型

  • 2、参数是引用类型 - 这种类型需要从对象中拿到对象的属性值

/**
* 构建锁的业务数据
* @author huangle
* @date 2023/5/5 15:01
*/
@Target({ElementType.PARAMETER})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface LockField {
   String[] fieldNames() default {};
}

定义切面

重点就在这个切面里面,我们需要在这里完成key的合成,锁的获取与释放。整个过程可以分为以下几步

  • 获取锁的基本信息,构建key

  • 加锁,执行业务

  • 业务完成,释放锁

/**
* 自动锁切面
* 处理加锁解锁逻辑
*
* @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();
       // 获取方法参数
       Parameter[] parameters = method.getParameters();
       StringBuilder lockKeyStr = new StringBuilder(prefix);
       Object[] args = joinPoint.getArgs();
       // 遍历参数
       int index = -1;
       LockField lockField;
       // 构建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("加锁失败!");
           }
           // 执行业务
           return joinPoint.proceed();
       } finally {
           // 释放锁
           if (lockFlag) {
               lock.unlock();
               LOGGER.info("释放锁完成,key:{}",lockKey);
           }
       }
   }
}

获取业务属性

这个是一个获取对象中字段的工具类,在一些常用的工具类里面也有实现,可以直接使用也可以自己实现一个

/**
* @author huangle
* @date 2023/5/5 15:17
*/
public class ReflectionUtil {
   public static List&lt;Object&gt; getFiledValues(Class&lt;?&gt; type, Object target, String[] fieldNames) throws IllegalAccessException {
       List&lt;Field&gt; fields = getFields(type, fieldNames);
       List&lt;Object&gt; 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&lt;Field&gt; getFields(Class&lt;?&gt; claszz, String[] fieldNames) {
       if (fieldNames != null &amp;&amp; fieldNames.length != 0) {
           List&lt;String&gt; needFieldList = Arrays.asList(fieldNames);
           List&lt;Field&gt; matchFieldList = new ArrayList();
           List&lt;Field&gt; 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&lt;Field&gt; getAllField(Class&lt;?&gt; claszz) {
       if (claszz == null) {
           return Collections.EMPTY_LIST;
       } else {
           List&lt;Field&gt; list = new ArrayList();
           do {
               Field[] array = claszz.getDeclaredFields();
               list.addAll(Arrays.asList(array));
               claszz = claszz.getSuperclass();
           } while(claszz != null &amp;&amp; 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 中内容
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.anoxia.lock.config.LockAutoConfig

测试

我们先打包这个sarter,然后导入到一个项目里面(打包导入的过程就不说了,自己去看一下就可以) 直接上测试类,下面执行后可以看到锁已经完成了释放。如果业务抛出异常导致中断也不用担心锁不会释放的问题,因为我们是在 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();
   }
}

Springboot-Starter造轮子之自动锁组件lock-starter实现

Springboot-Starter造轮子之自动锁组件lock-starter实现

来源:https://juejin.cn/post/7229615985807982649

标签:Springboot-Starter,造轮子,lock-starter,自动锁
0
投稿

猜你喜欢

  • C# 微信支付回调验签处理的实现

    2021-07-27 01:57:34
  • Android实现简单实用的垂直进度条

    2023-10-22 19:10:13
  • 关于SpringBoot中controller参数校验的使用

    2023-02-24 04:24:10
  • C# 判断字符串第一位是否为数字

    2023-05-29 20:06:44
  • Android单选按钮对话框用法实例分析

    2023-03-27 17:10:52
  • Java面向对象编程的三大特征

    2023-09-19 06:20:34
  • Intellij IDEA中启动多个微服务(开启Run Dashboard管理)

    2022-01-11 02:25:00
  • 最新IntelliJ IDEA2017.3 激活方式

    2023-08-12 00:47:24
  • 详解Java数据结构和算法(有序数组和二分查找)

    2023-04-08 13:38:07
  • Spring Security如何实现升级密码加密方式详解

    2023-09-02 08:47:31
  • Spring与Struts整合之让Spring管理控制器操作示例

    2022-08-22 12:53:46
  • Android编程之ListView和EditText发布帖子隐藏软键盘功能详解

    2023-11-06 23:09:55
  • SpringBoot构建RESTful API的实现示例

    2022-04-13 14:45:08
  • java实现操作系统中的最佳置换Optimal算法

    2023-10-26 10:27:13
  • 描述C#多线程中lock关键字的使用分析

    2021-12-31 08:42:32
  • Android编程实现小说阅读器滑动效果的方法

    2021-10-26 06:44:06
  • IntelliJ IDEA 15款超级牛逼插件推荐(自用,超级牛逼)

    2023-10-10 05:51:22
  • C#中子类调用父类的实现方法

    2023-08-17 09:09:52
  • C#获得程序的根目录以及判断文件是否存在的实例讲解

    2022-12-07 18:00:27
  • C#中==(双等于号)与equals()区别详解

    2021-09-21 18:49:42
  • asp之家 软件编程 m.aspxhome.com