开发者

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

开发者 https://www.devze.com 2023-05-07 10:34 出处:网络 作者: Anoxia1
目录前言实现定义注解AutoLock 注解LockField 注解定义切面获取业务属性配置自动注入测试总结前言
目录
  • 前言
  • 实现
  • 定义注解
    • AutoLock 注解
    • LockField 注解
    • 定义切面
    • 获取业务属性
    • 配置自动注入
  • 测试
    • 总结

      前言

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

      在日常业务开发的过程中,我们经常会遇到存在高并发的场景,这个时候都会选择使用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 @interf编程客栈ace AutoLock {
          /**
           * 锁前缀
           */
          String prefix() default "anoxia:lock";
          /**
           * 加锁时间
           */
          www.devze.comlong 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;
                开发者_JAVA入门  }
                  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;
          }
          @PosjstMapping("/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实现

      总结

      很多时候,一些公共的业务逻辑都可以被抽象出来成为一个独立的组件而存在,我们可以在日常开发过程中,不断的去思考和寻找看哪些可以被抽象出来,哪些可以更加简化一些。然后尝试去抽象出一个组件出来,这样的话不但可以锻炼自己的能力,还可以得到一些很好用的工具,当然自己抽出的组件可以存在问题,但是慢慢的锻炼下来,总会变的越来越好。 怎么说呢,尝试去做,能不能做好再说,做不好就一次又一次的去做。

      以上就是Springboot-Starter造轮子之自动锁组件(lock-starter)的详细内容,更多关于Springboot-Starter自动锁组件(lock-starter)的资javascript料请关注我们其它相关文章!

      0

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      关注公众号