SpringBoot-Starter 自动锁组件

这篇具有很好参考价值的文章主要介绍了SpringBoot-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;

    /**
     * 等待时间,-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<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 中内容
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 自动锁组件,设计,SpringBoot,spring boot,java,后端

SpringBoot-Starter 自动锁组件,设计,SpringBoot,spring boot,java,后端文章来源地址https://www.toymoban.com/news/detail-802176.html

到了这里,关于SpringBoot-Starter 自动锁组件的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Springboot】| 从深入自动配置原理到实现 自定义Springboot starter

    Springboot starter 是SpringBoot的一个重要概念,是“一站式服务 (one-stop)”的依赖 Jar 包包含 Spring 以及相关技术(比如 Redis)的所有依赖提供了自动配置的功能,开箱即用提供了良好的依赖管理,避免了包遗漏、版本冲突等问题。 简单来说, Springboot starter 提供了一种自动配置的机制

    2024年02月11日
    浏览(39)
  • Spring Boot Starter设计实现

    Starter 是 Spring Boot 非常重要的一个硬核功能。 通过 Starter 我们可以快速的引入一个功能或模块,而无须关心模块依赖的其它组件。关于配置,Spring Boot 采用“约定大于配置”的设计理念,Starter 一般都会提供默认配置,只有当我们有特殊需求的时候,才需要在 application.yaml 里

    2024年01月18日
    浏览(48)
  • 27、springboot自定义第三方框架和Starter组件及其测试完整版

    所谓的自动配置,就是通过一个配置类,然后这个配置类在我们容器中定义了大量的bean,然后这些bean也不是直接定义,它是结合了条件注解,只有在某些特定的条件下,才会生效,这样我们的自动配置就可以根据我们的环境的配置(如yml配置文件),根据我们这个应用程序

    2024年02月12日
    浏览(43)
  • redisson-spring-boot-starter 自动化配置源码解析

    redisson-spring-boot-starter:3.25.2 此starter会自动注册RedissonClient Bean 并可通过注册RedissonAutoConfigurationCustomizer Bean实现配置自定义 spring-boot:2.7以上 org.redisson.spring.starter.RedissonAutoConfigurationV2 spring-boot:2.6以下 org.redisson.spring.starter.RedissonAutoConfiguration

    2024年01月17日
    浏览(83)
  • SpringBoot——原理(自动配置_案例(自定义阿里云文件上starter))

    本文同步更新于鼠鼠之家 starter就是springboot中的起步依赖,虽然springboot已经提供了很多的起步依赖,但是在实际项目开发中可能会用到和第三方的技术,不是所有第三方在springboot中都有收录。 比如之前文章中有用到过的阿里云OSS,阿里云并没有提供起步依赖,导致每次使用

    2024年02月06日
    浏览(46)
  • SpringBoot——原理(自动配置_案例(自定义阿里云文件上传starter))

    本文同步更新于鼠鼠之家 starter就是springboot中的起步依赖,虽然springboot已经提供了很多的起步依赖,但是在实际项目开发中可能会用到和第三方的技术,不是所有第三方在springboot中都有收录。 比如之前文章中有用到过的阿里云OSS,阿里云并没有提供起步依赖,导致每次使用

    2024年02月07日
    浏览(38)
  • SpringBoot+jasypt-spring-boot-starter实现配置文件明文加密

    springboot:2.1.4.RELEASE JDK:8 jasypt-spring-boot-starter:3.0.2 Jasypt默认算法为PBEWithMD5AndDES,该算法需要一个加密密钥,可以在应用启动时指定(环境变量)。也可以直接写入配置文件 3.1 application.properties配置文件版 加密后,可删除jasypt.encryptor.password配置;发版时可在命令行中配置 3.2 函数

    2024年02月15日
    浏览(42)
  • SpringBoot3自动配置流程 SPI机制 核心注解 自定义starter

    导入 starter 依赖导入 autoconfigure 寻找类路径下 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件 启动,加载所有 自动配置类 xxxAutoConfiguration 给容器中配置功能 组件 组件参数 绑定到 属性类 中。 xxxProperties 属性类 和 配置文件 前缀项绑定 @Contional 派生的条件

    2024年02月16日
    浏览(45)
  • Springboot实战之spring-boot-starter-data-elasticsearch搭建ES搜索接口

    本教程是本人亲自实战的,然后运行起来的全部步骤。 环境 Elasticsearch 7.15.2 Kibana 7.15.2 springboot 2.6.4 以及对应的spring-boot-starter-web和spring-boot-starter-data-elasticsearch fastjson 1.2.97 安装好Elasticsearch7.15.2以及对应的Kibana。 去Springboot Start 新建项目 使用 devtools 创建 number_of_shards 数据分

    2023年04月08日
    浏览(51)
  • 自定义redission装配和集成分布式开源限流业务组件ratelimiter-spring-boot-starter的正确姿势

    自定义redission装配和集成分布式开源限流业务组件ratelimiter-spring-boot-starter的正确姿势   由于使用了redisson-spring-boot-starter,在自定义redisson装配的时候会被redisson-spring-boot-starter里面的start默认装配了,同时在使用开源分布式限流组件ratelimiter-spring-boot-starter的时候,这个里面

    2024年02月07日
    浏览(55)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包