47. 从零开始学springboot: spel结合redisson实现动态参数分布式锁

Spring Cache使用Redis自定义缓存key 本篇博客,简单的介绍了sprig cache,spring cache如何使用redis作为缓存,已经自定义缓存key策略,以及通过配置缓存过期时间 阅读详情

前言

随着分布式服务的烂大街, 不可避免的就是公共资源的争用问题, 对开发来说, 本质上就是如何限流加锁的问题.

上章简单介绍了spel表达式的一些应用案例, 本章则结合spel和redisson来实现一个分布式锁注解.

Redisson简介

Redisson 是一个高级的、分布式协调Redis客服端,能帮助用户在分布式环境中轻松实现一些Java的对象.
Redisson、Jedis、Lettuce 是三个不同的操作 Redis 的客户端.

Jedis、Lettuce 的 API 更侧重对 Reids 数据库的 CRUD(增删改查),而 Redisson API 侧重于分布式开发.

关于Redisson的更多介绍大家自行查阅资料了解.

实例

引入必要pom依赖 redis、aop、redisson

<!-- Redis依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 引入Aop支持 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Redisson分布式框架依赖 -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
</dependency>

新增redisson的配置文件RedissonConfig

@Configuration
public class RedissonConfig {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private String port;

    @Value("${spring.redis.password}")
    private String password;

    @Bean
    @ConditionalOnMissingBean(name = "redisson")
    public Redisson redisson() {
        org.redisson.config.Config config = new org.redisson.config.Config();
        config.useSingleServer().setAddress("redis://" + host + ":" + port);
        return (Redisson) Redisson.create(config);
    }
}

新增工具类spel

public class SpelUtil {

    /**
     * 用于SpEL表达式解析.
     */
    private static final SpelExpressionParser parser = new SpelExpressionParser();

    /**
     * 用于获取方法参数定义名字.
     */
    private static final DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

    /**
     * 解析SpEL表达式
     */
    public static String generateKeyBySpEL(String spELStr, ProceedingJoinPoint joinPoint) {
        // 通过joinPoint获取被注解方法
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        // 使用Spring的DefaultParameterNameDiscoverer获取方法形参名数组
        String[] paramNames = nameDiscoverer.getParameterNames(method);
        // 解析过后的Spring表达式对象
        Expression expression = parser.parseExpression(spELStr);
        // Spring的表达式上下文对象
        EvaluationContext context = new StandardEvaluationContext();
        // 通过joinPoint获取被注解方法的形参
        Object[] args = joinPoint.getArgs();
        // 给上下文赋值
        for (int i = 0; i < args.length; i++) {
            context.setVariable(paramNames[i], args[i]);
        }
        return expression.getValue(context).toString();
    }
}

新增哥aop工具类

public class AopUtil extends AopUtils {
    /**
     * 判断环绕通知目标方法返回值是否为Void
     *
     * @param joinPoint
     * @return
     */
    public static boolean isReturnVoid(ProceedingJoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        Class returnType = ((MethodSignature) signature).getReturnType();
        return returnType.equals(Void.TYPE);
    }
}

声明分布式锁注解DistributedLock

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {

    /**
     * 锁超时时间【单位秒,默认5秒】
     *
     * @return
     */
    long expireTime() default 5;

    /**
     * 分布式缓存Key【SpEL表达式】
     *
     * @return
     */
    String lockKey();

    /**
     * 获取锁失败默认提示消息
     *
     * @return
     */
    String message() default "获取分布式锁失败";
}

实现分布式锁切面DistributedLockAop

@Slf4j
@Aspect
@Component
public class DistributedLockAop {

    @PostConstruct
    public void init() {
        log.info("DistributedLockAop init ......");
    }

    @Autowired(required = false)
    private Redisson redisson;

    private static final String LOCK_KEY_PREFIX = "LOCK:";

    /**
     * 拦截加了DistributedTask注解的方法请求
     *
     * @param joinPoint
     * @param distributedLock
     * @return
     * @throws Throwable
     */
    @Around("@annotation(distributedLock)")
    public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
        Object result = null;

        String lockKey = LOCK_KEY_PREFIX + SpelUtil.generateKeyBySpEL(distributedLock.lockKey(), joinPoint);

        RLock lock = this.redisson.getLock(lockKey);

        boolean locked = false;
        try {
            // 尝试获取分布式锁, 并设置超时时间
            locked = lock.tryLock(0, distributedLock.expireTime(), TimeUnit.SECONDS);
            if (!locked) {
                log.info("#Redisson分布式锁# 加锁失败: [lockKey: {}, ThreadName :{}]", lockKey, Thread.currentThread().getName());
                // 判断目标方法是否有返回值
                if (AopUtil.isReturnVoid(joinPoint)) {
                    return null;
                } else {
                    return R.fail(distributedLock.message());
                }
            }
            log.info("#Redisson分布式锁# 加锁成功: [lockKey: {}, ThreadName :{}]", lockKey, Thread.currentThread().getName());

            // 执行目标方法
            result = joinPoint.proceed();
        } finally {
            if (locked) {
                lock.unlock();
                log.info("#Redisson分布式锁# 解锁成功: [lockKey: {}, ThreadName :{}]", lockKey, Thread.currentThread().getName());
            }
        }
        return result;
    }
}

新增个返回实体R

@Getter
@Setter
@NoArgsConstructor
@JsonSerialize
@JsonInclude(Include.NON_NULL)
public class R implements Serializable {

    private static final long serialVersionUID = -4888265702222003946L;

    // 成功状态码
    public static final Integer SUCCESS_C0DE = Integer.valueOf(0);

    // 失败状态码
    public static final Integer FAIL_CODE = Integer.valueOf(-1);

    // 接口状态码
    private Integer code;

    // 提示消息
    private String message;

    // 接口业务数据
    private Object result;

    public R(Integer code, String message) {
        super();
        this.code = code;
        this.message = message;
    }

    public R(Integer code, Object result) {
        super();
        this.code = code;
        this.result = result;
    }

    public R(Integer code) {
        super();
        this.code = code;
    }

    public static R success(Object result) {
        return new R(R.SUCCESS_C0DE, result);
    }

    public static R success() {
        return new R(R.SUCCESS_C0DE);
    }

    public static R fail(String errorMsg) {
        return new R(R.FAIL_CODE, errorMsg);
    }


    public static R fail(Integer errCode) {
        if (errCode == SUCCESS_C0DE) {
            return new R(FAIL_CODE);
        }
        return new R(errCode);
    }

    public static R fail(Integer errCode, String errMessage) {
        return new R(errCode, errMessage);
    }
}

新增实体类user

@Getter
@Setter
@NoArgsConstructor
@JsonSerialize
@JsonInclude(Include.NON_NULL)
public class User implements Serializable {
    private static final long serialVersionUID = -5271056342318564651L;
    private String name;
    private Long id;
}

新增控制器DistributedLockController

@Slf4j
@RestController
public class DistributedLockController {

    /**
     * 测试:lockKey动态值【SpEL表达式】 + 常量
     */
    @DistributedLock(lockKey = "'userId-' + #userId", expireTime = 100)
    @GetMapping("/lock2/{userId}")
    public R lock2(@PathVariable("userId") Long userId) {
        for (int i = 0; i < 1000000; i++) {
            log.info("waiting......");
        }
        return R.success();
    }


    /**
     * 测试:lockKey动态值【SpEL表达式】 + 常量
     */
    @DistributedLock(lockKey = "'userId-' + #user.id", expireTime = 100)
    @PostMapping("/lock2")
    public R lock2(@RequestBody User user) {
        for (int i = 0; i < 1000000; i++) {
            log.info("waiting......");
        }
        return R.success();
    }
}

最后加上redis的配置application.properties

#****************Redis Config**********************
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=

项目地址

https://github.com/MrCoderStack/SpringBootDemo/tree/master/sb-distributed-lock-annotations

请关注我的订阅号

订阅号.png

手动撸一个Redis缓存注解 一:设计思路 流程图: 注解-AOP模式: 利用注解和AOP模式可以非侵入式的实现缓存功能,spring有自带的Cacheable, 但是不能每个缓存自定义过期时间,还有一些自定义功能,所以选择重新写一个 需要用到的东西: 1.AOP包->实现AOP功能 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>sp 阅读详情

相关推荐

关于solidworks无法获得下列许可SOLIDWORKS Standard的解决方法

在使用solid works的时候,出现无法获得下列许可SOLIDWORKS Standard,刚开始还以为软件出了问题,经过一方折腾找到了解决方法。希望这个方法可以帮助到您 找到这个服务 问题到这里就已经得到了解决,但更大的问题是,要是每次遇见这种都要这么麻烦,岂不是很累?这里也提供一种方法。考虑到每个人的情况,下面说的可能有一点啰嗦。 !!!!!!注意那个在此期间之后重新启动服...

Chaosna13的博客 7万+

用表达式获取对象值-SpEL工具类

【代码】用表达式获取对象值-SpEL工具类。

xsd_dongye的博客 628

PLDM入门(七):FRU Data Format

每个字段为TLV(Type-Length-Value)格式,具体类型、长度范围和值定义需参考。通过条件过滤(记录句柄、类型、字段类型等)分块获取FRU记录数据(可选功能)。整个FRU数据表由多个FRU记录数据块组成,每个数据块对应表2的结构(即包含。决定,每个字段按顺序排列(Type→Length→Value)通过分块传输方式写入FRU记录表数据(需终端支持写入操作)。(文档未提供表5内容,需结合标准文档完整版)。、字段数量、编码类型及具体字段的TLV数据)。无请求数据(空请求)。

weixin_44032634的博客 1762

spel表达式工具类

spel表达式

weixin_59244784的博客 788

发那科机器人Modbus通讯配置全攻略:从IP设置到信号调试

本文详细介绍了发那科机器人Modbus通讯的配置方法,从基础认知到IP设置、端口配置、信号调试及故障排查。重点讲解了UOP和DI/DO信号的配置技巧,帮助工程师快速实现机器人与PLC的稳定通讯,提升自动化系统效率。

weixin_27872611的博客 620

Windows非分页缓冲池内存泄漏实战:PoolMon与RAMMap高效排查指南

本文详细介绍了Windows系统中非分页缓冲池内存泄漏的实战排查方法。当系统出现卡顿且内存占用异常增长时,通过RAMMap工具快速定位非分页缓冲池异常,并利用PoolMon监控内核内存分配标签,精准锁定泄漏源。文章提供了从确认症状、安装工具到使用findstr命令溯源驱动文件的完整五步排查流程,并总结了常见泄漏标签与解决方案,帮助用户高效解决深层系统内存问题。

weixin_28455801的博客 1008

SpEL表达式详解

`Spring Expression Language`(简称SpEL)是一种功能强大的表达式语言,是`spring`提供的,该语言类似于JSP当中的EL表达式。但提供了很多额外的功能,最出色的就是`函数调用`和`简单字符串的模板函数`。他需要使用`spring`提供的解析器来解析,但是他不依赖于`spring`,可以独立使用。在`spring`程序当中,我们不用管解析器,由`spring`来帮我们自动构建。我们只需要写想要表达的字符串,交给`spring`来进行解析即可。

怪咖@的博客 3万+

氮化镓GaN电机驱动实战:从器件原理到EPC驱动中心

电机驱动是工业自动化和电动交通的核心,功率器件的选择直接决定系统效率与可靠性。传统硅基IGBT和MOSFET在开关速度、损耗方面存在瓶颈,而氮化镓(GaN)作为宽禁带半导体,凭借高电子迁移率、低栅极电荷和近乎为零的反向恢复电荷,正成为电机驱动领域的热门选择。EPC建立电机驱动中心,提供完整的评估平台,覆盖从低压家电到中压伺服的应用场景。文章从GaN器件原理出发,对比传统硅器件,详解三相BLDC/PMSM驱动系统的搭建、栅极驱动与Layout设计、死区时间设置及参数计算实例,并分享测试中遇到的振铃、EMI等高

weixin_29168675的博客 321

SpEl工具类

【代码】SpEl工具类。

yxw678的博客 593

手把手教你学Simulink--基于Simulink的近端策略优化(PPO)路径规划仿真建模

本文详细介绍了如何利用Simulink进行近端策略优化(PPO)路径规划的仿真建模。首先,文章概述了PPO路径规划的特点和挑战,强调了其在连续或离散动作空间问题中的高效性和稳定性。接着,列出了所需的工具和环境,包括MATLAB/Simulink、Reinforcement Learning Toolbox等。随后,文章分步骤详细讲解了从定义任务需求到验证与分析的整个仿真建模过程,包括创建Simulink项目、集成智能体模型、构建环境模型、编写PPO路径规划逻辑、将逻辑集成到Simulink模型中以及最终的验

MHD0815的博客 165

FPGA 学习笔记:Vitis IDE launch failed 的解决方法

FPGA 学习笔记:Vitis IDE launch failed 的解决方法

坚韧与专注 1万+

Spring SpEL

Spring SpEL 文章目录Spring SpEL一、JVM动态语言二、SpEL表达式三、SpEL的核心接口3.1 `ExpressionParser`(1) 字符串的拼接3.2 `EvaluationContext`(2) 针对特定实例对象的属性进行求值3.3 `SpelCompiler`可以将字节码直接编译成字节码四、SpEL表达式4.1 文本字符解析4.2 对象属性解析4.3 数组、集合类型解析4.4 方法解析4.5 操作符解析(1) 关系操作符(2)逻辑操作符(3)算术运算操作符4.6 安全导航

hefrankeleyn的博客 5210

SAP_MM_配额安排_PA认证总结

配额安排是企业进行供应商管理的一种方式。通过配额管理,企业可以与多家供应商保持业务往来,以保证某些紧缺原材料的正常供货。此外,企业也可以通过不同的配额比例,提高供应商优化服务、降低价格的积极性。

java_zhong1990的专栏 1020

Redisson分布式锁RedissonLock介绍

转载地址 https://www.cnblogs.com/qdhxhz/p/11055426.html RedissonLock 这篇主要讲RedissonLock和RLock。Redisson分布式锁的实现是基于RLock接口,RedissonLock实现RLock接口。 一、RLock接口 1、概念 public interface RLock extends Lock, RExpirable, RLockAsync 很明显RLock是继承Lock锁,所以他有Lock锁的所有特性,比如lock、unl

张俊杰 的博客 6358

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

使用Spring AOP基于Redisson快速实现分布式锁

版本 SpringBoot:2.2.6.RELEASE Redisson:3.12.5 依赖 <!--核心依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis&l...

LIUUUU 1151

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

SpEL表达式

总结自:www.jianshu.com/p/61f7c6fe03ec SpEL是一种表达式语言,可以与spring应用程序运行时的对象交互,可简化开发,减少一些逻辑、配置的编写。 既能在配置文件中使用,也能在注解中使用(常用于@Value等)。 备注:SpEL都是#{}的格式 字面值 整数 #{5} 浮点数 #{3.1415} 科学计数法 #{9.87E4} 字...

weixin_30307267的博客 226

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

Spring的表达式语言(SpEL)使用

Spring表达式语言(Spring Expression Language,简称SpEL)是Spring框架提供的一种强大的表达式语言,它在Spring 2.0版本引入。SpEL的设计灵感来源于传统的EL(Expression Language),即JSP中的表达式语言,但它提供了更多的功能和灵活性。SpEL是Spring框架中一个非常有用的工具,它允许开发者在许多不同的上下文中以声明式的方式编写和评估表达式,从而提高了代码的灵活性和可读性。在方法中,SpEL可以用于调用其他Bean的方法。

编程小弟的博客 872

【java】aop中使用spel表达式工具类

【代码】【java】aop中使用spel表达式工具类。

muguazhi的博客 357

SpEL(2)

基本表达式 字面量表达式 字符串、数字类型(int、long、float、double)、boolean、null类型 类型 示例 字符串 String str=parser.parseExpression(“‘Hello’”).getValue(String.class); 数字类型 int i=parser.parseExpression(“1

Envieol 828

Spring中的表达式语言SpEL详解

Spring 表达式语言(简称SpEL):是一个支持运行时查询和操作对象图的强大的表达式语言。 语法类似于 EL:SpEL 使用 #{…} 作为定界符,所有在大框号中的字符都将被认为是 SpEL,SpEL 为 bean 的属性进行动态赋值提供了便利 通过 SpEL 可以实现: - 通过 bean 的 id 对 bean 进行引用 - 调用方法以及引用对象中的属性 - 计

小小默:进无止境 1824

Spring依赖注入 1.构造函数注入 2. 属性注入 3. 集合属性注入(DI)

Spring注入方法

qq_45844443的博客 642

天机学堂7--Redisson自定义注解AOP以及SPEL表达式实现分布式锁

通知类:里面的切点表达式,public是方法返回类型,路径…*是service及其子包下的类 后面的.*是任意方法 (…)指任意参数(@annotation(路径.实现类中方法的注解)@Around("@annotation(xxx类)")以上图为例,只要方法加了@printTime注解,方法就是切点,再走这个方法之前就会走环绕通知@Around()around()方法。

juli_eyre的博客 1445

自定义注解支持SpEL表达式(动态参数)

实际生产中一个案例:利用AOP生成用户操作日志代码直接复制过去就可以测试,亲测可用 2.定义spel解析工具类 3.定义切面类 4.方法上使用日志注解

zhangkaixuan456的博客 3394

SpringBoot分布式锁-Distributed-Lock

分布式锁: Distributed-Lock 一、简介 ​ Distributed-Lock为解决分布式系统中资源争夺而生。在当前分布式、微服务架构盛行的年代,多个服务可能会对第一个资源进行抢夺,如果不加以控制,可能导致无法想象有点后果,或者有一些操作不可以同时多次操作。 ​ 1、数据的存储目前只支持三种:Redis、Zookeeper、Mysql,推荐使用Redis。 ​ 2、支持超时自动释放锁,防止死锁问题 ​ 3、使用简单方便,侵入式较低,代码只需要一个注解 ​ 4、锁的资源ID以

weixin_33613947的博客 9389

spring+aop+redis+spEL防重复提交

spring+aop+redis+spEL防重复提交详解

卓越之识论道,平常之识论事,狭隘之识论人 637

SpEL表达式各数据类型基本使用举例

SpEL是Spring提供的一种的表达式语言,支持在运行时查询和操作对象。SpEL并不直接与Spring相关联,可以独立使用。

皮球 1936
上一篇: 46. 从零开始学springboot: spel结合aop实现动态传参
下一篇: 48. 从零开始学springboot: 接入RocketMQ
码哥说
博客等级 码龄8年 5645粉丝 172原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码哥说

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值