我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

前言

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

在日常业务开发的过程中,我们经常会遇到存在高并发的场景,这个时候都会选择使用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;
    
    long waitTime() default -1;
    
    TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
}

LockField 注解

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

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

@Target({ElementType.PARAMETER})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface LockField {
    String[] fieldNames() default {};
}

定义切面

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

  • 获取锁的基本信息,构建key
  • 加锁,执行业务
  • 业务完成,释放锁

@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() {
    }
    
    @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);
            }
        }
    }
}

获取业务属性

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


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来完成注入。注入后,我们就可以直接去使用这个封装好的锁了。


@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 中释放锁的


@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)的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

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

这篇文章主要为大家介绍了Springboot-Starter造轮子之自动锁组件lock-starter实现详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-05-19

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录