springboot中设计基于Redisson的分布式锁注解

news2024/11/21 18:54:58

如何使用AOP设计一个分布式锁注解?

1、在pom.xml中配置依赖

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.26</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
        </dependency>

2、逻辑代码

创建一下多个文件夹,并复制粘贴进代码

2.1、RedissonConfig.java

需要配置一下Redisson

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Redisson 配置
 *
 */
@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://192.168.57.111:6379") // Redis地址
                .setPassword(null) // 如果没有密码,设置为null
                .setDatabase(0); // 使用的Redis数据库索引

        RedissonClient redissonClient = Redisson.create(config);
        // 测试连接
        try {
            redissonClient.getKeys().count();
            System.out.println("Redisson connected to Redis successfully.");
        } catch (Exception e) {
            System.err.println("Failed to connect to Redis: " + e.getMessage());
        }

        return redissonClient;
    }
}

需要修改setAddress("redis://192.168.57.111:6379")中的地址为自己的redis地址

2.2、DistributedLock.java

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD) // 作用于方法
@Retention(RetentionPolicy.RUNTIME) // 注解在运行时生效
public @interface DistributedLock {
    String prefix() default "lock:"; // 锁前缀,默认为 "lock:"
    String key() default ""; // 锁的Key,支持SpEL表达式
    long leaseTime() default 30; // 锁的默认持有时间,单位秒
    long waitTime() default 10; // 获取锁的等待时间,单位秒
}

2.3、DistributedLockAspect.java

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class DistributedLockAspect {

    @Autowired
    private RedissonClient redissonClient; // 注入 Redisson 客户端


    /**
     * 定义切入点,匹配所有使用 @DistributedLock 注解的方法
     * @param distributedLock 分布式锁注解对象
     */
    @Pointcut("@annotation(distributedLock)")
    public void distributedLockPointcut(DistributedLock distributedLock) {}

    /**
     * 环绕通知:在目标方法执行前后处理分布式锁逻辑
     *
     * @param joinPoint 切点,表示目标方法的执行点
     * @param distributedLock 注解,用于获取注解属性值
     * @return 目标方法的返回值
     * @throws Throwable 当目标方法抛出异常时向上抛出
     */
    @Around("distributedLockPointcut(distributedLock)")
    public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
        String lockKey = buildLockKey(distributedLock.prefix(),distributedLock.key(), joinPoint); // 生成锁Key
        RLock lock = redissonClient.getLock(lockKey);

        boolean isLocked = false;
        try {
            while (true) {
                // 尝试获取锁
                isLocked = lock.tryLock(distributedLock.waitTime(), distributedLock.leaseTime(), TimeUnit.SECONDS);

                if (isLocked) {
                    System.out.println(Thread.currentThread().getName() + " 成功获取锁,key: " + lockKey);
                    return joinPoint.proceed(); // 执行目标方法
                } else {
                    System.out.println(Thread.currentThread().getName() + " 未获取到锁,key: " + lockKey + ",重试中...");
                    // 等待一段时间后再重试
                    Thread.sleep(500);
                }
            }
        } finally {
            if (isLocked && lock.isHeldByCurrentThread()) {
                lock.unlock(); // 释放锁
                System.out.println(Thread.currentThread().getName() + " 已释放锁,key: " + lockKey);
            }
        }
    }

    private String buildLockKey(String prefix, String key, ProceedingJoinPoint joinPoint) {
        if (key.isEmpty()) {
            throw new IllegalArgumentException("Lock key cannot be empty");
        }

        // 拼接锁前缀和原始键
        String rawKey = prefix + key;

        // 将拼接后的键通过 MD5 生成唯一的锁键
        return generateMD5(rawKey);
    }

    /**
     * 使用 MD5 生成锁键
     * @param input 原始键
     * @return MD5 生成的锁键
     */
    private String generateMD5(String input) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] digest = md.digest(input.getBytes());

            // 转换为 32 位十六进制字符串
            StringBuilder hexString = new StringBuilder();
            for (byte b : digest) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }
            return hexString.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Failed to generate MD5 hash", e);
        }
    }

}

这三步做完之后,该注解就能用了。

3、业务模拟测试

根据以下创建文件,并写入代码

3.1、InventoryService.java

这里测试可以itemId为键加锁

import com.pshao.charplatform.utils.distributedLock.DistributedLock;
import org.springframework.stereotype.Service;

@Service
public class InventoryService {

    @DistributedLock(prefix = "stock",key = "#itemId", leaseTime = 10, waitTime = 1)
    public void reduceStock(Long itemId, int quantity) {
        System.out.println(Thread.currentThread().getName() + " 正在处理库存扣减: itemId=" + itemId + ", quantity=" + quantity);
        try {
            Thread.sleep(2000); // 模拟耗时操作,修改为两秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 完成库存扣减: itemId=" + itemId);
    }

}

这里可以输入以下多个参数,key是必须的,其他可以不输入

String prefix() default "lock:"; // 锁前缀,默认为 "lock:"
String key() default ""; // 锁的Key
long leaseTime() default 30; // 锁的默认持有时间,单位秒
long waitTime() default 10; // 获取锁的等待时间,单位秒

3.2、DistributedLockApplication.java

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@SpringBootApplication(scanBasePackages = "com.pshao.charplatform")
public class DistributedLockApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(DistributedLockApplication.class, args);
        InventoryService inventoryService = context.getBean(InventoryService.class);

        ExecutorService executor = Executors.newFixedThreadPool(3); // 创建3个线程

        // 模拟多个线程竞争同一资源
        for (int i = 0; i < 3; i++) {
            executor.submit(() -> inventoryService.reduceStock(123L, 10));
        }

        executor.shutdown();
    }
}

3.3、运行查看测试结果

测试成功! 

如果运行后出现,类似以下日志

Action: Correct the classpath of your application so that it contains compatible versions of the classes org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator and org.springframework.util.ClassUtils

考虑是版本冲突,可以参考Correct the classpath of your application so that it contains compatible versions......版本不兼容解决方法-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2244827.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

绕过CDN寻找真实IP

在新型涉网案件中&#xff0c;我们在搜集到目标主站之后常常需要获取对方网站的真实IP去进一步的信息搜集&#xff0c;但是现在网站大多都部署了CDN&#xff0c;将资源部署分发到边缘服务器&#xff0c;实现均衡负载&#xff0c;降低网络堵塞&#xff0c;让用户能够更快地访问自…

【Redis】redis缓存击穿,缓存雪崩,缓存穿透

一、什么是缓存&#xff1f; 缓存就是与数据交互中的缓冲区&#xff0c;它一般存储在内存中且读写效率高&#xff0c;提高响应时间提高并发性能&#xff0c;如果访问数据的话可以先访问缓存&#xff0c;避免数据查询直接操作数据库&#xff0c;造成后端压力过大。 但是可能会面…

linux复习5:C prog

编辑 缩排 为了使C源代码更加整洁易读&#xff0c;可以使用一些工具来自动格式化代码&#xff0c;例如cb&#xff08;C程序美化器&#xff09;、bcpp&#xff08;C美化器&#xff09;和indent等。 编译 编译并链接C文件 gcc hello.c -o hello 将 hello.c 编译并链接成可执行文…

uni-app快速入门(十)--常用内置组件(下)

本文介绍uni-app的textarea多行文本框组件、web-view组件、image图片组件、switch开关组件、audio音频组件、video视频组件。 一、textarea多行文本框组件 textarea组件在HTML 中相信大家非常熟悉&#xff0c;组件的官方介绍见&#xff1a; textarea | uni-app官网uni-app,un…

世界坐标系、相机坐标系、图像物理坐标系、像素平面坐标系

坐标系及其转换在计算机视觉领域占据核心地位。理解如何从一个坐标系转换到另一个坐标系&#xff0c;不仅是理论上的需要&#xff0c;也是实际应用中不可或缺的技能。 一、世界坐标系的定义 世界坐标系是一个全局的坐标系统&#xff0c;用于定义场景中物体的位置。在这个坐标…

机器学习笔记——聚类算法(Kmeans、GMM-使用EM优化)

本笔记介绍机器学习中常见的聚类算法&#xff08;Kmeans、GMM-使用EM优化&#xff09;。 文章目录 聚类K-Means工作原理特点 K-Medoids工作原理特点 Mini-Batch K-Means工作原理特点 K-Means&#xff08;重要&#xff09;工作原理特点 总结K的选值1. 肘部法则&#xff08;Elbow…

浅议Flink中的通讯工具: Akka

在Flink中&#xff0c;各个组件之间需要频繁交换数据和控制信息。Flink选择了基于Actor模型的Akka框架作为通信基础。 Akka是什么 Actor模型 Actor模型是用于单个进程中并发的场景。 在Actor模型中&#xff1a; ActorSystem负责管理actor生命周期 将每个实体视为独立的 Ac…

如何在react中使用react-monaco-editor渲染出一个编辑器

一、效果展示 二、基于vite配置 1.首先安装react-monaco-editor和monaco-editor包 npm add react-monaco-editor npm i monaco-editor 2.其次创建一个单独的文件&#xff08;此处是tsx、直接用app或者jsx也行&#xff09; import { useState, useEffect } from react impo…

孙玲:从流水线工人到谷歌程序员

这是《开发者说》的第24期&#xff0c;本期我们邀请的开发者是孙玲&#xff0c;她出生于湖南娄底一个贫穷的农村家庭&#xff0c;2009年高考落榜&#xff0c;她去了深圳一家电子厂&#xff0c;在流水线上给电池喷码&#xff0c;每天12个小时轮班&#xff0c;月薪2300&#xff0…

kali搭建pikachu靶场

前言&#xff1a; 总所周知搭个网站需要有apachemysqlphp&#xff0c;Apache是一个开源的Web服务器软件&#xff0c; MySQL是一种关系型数据库管理系统&#xff08;数据库&#xff09;&#xff0c;PHP是一种在服务器上执行的脚本语言 文章内容来自&#xff1a;【黑帽编程与攻…

android 使用MediaPlayer实现音乐播放--获取音乐数据

前面已经添加了权限&#xff0c;有权限后可以去数据库读取音乐文件&#xff0c;一般可以获取全部音乐、专辑、歌手、流派等。 1. 获取全部音乐数据 class MusicHelper {companion object {SuppressLint("Range")fun getMusic(context: Context): MutableList<Mu…

VMware Workstation 17.6.1

概述 目前 VMware Workstation Pro 发布了最新版 v17.6.1&#xff1a; 本月11号官宣&#xff1a;针对所有人免费提供&#xff0c;包括商业、教育和个人用户。 使用说明 软件安装 获取安装包后&#xff0c;双击默认安装即可&#xff1a; 一路单击下一步按钮&#xff1a; 等待…

Methode Electronics EDI 需求分析

Methode Electronics 是一家总部位于美国的全球性技术公司&#xff0c;专注于设计和制造用于多个行业的电子和电气组件&#xff0c;产品涵盖汽车、工业、电信、医疗设备以及消费电子等多个领域&#xff0c;提供创新的解决方案。 填写Methode_EDI_Parameters_Template Methode_…

uniapp自动注册机制:easycom

传统 Vue 项目中&#xff0c;我们需要注册、导入组件之后才能使用组件。 uniapp 框架提供了一种组件自动注册机制&#xff0c;只要你在 components 文件夹下新建的组件满足 /components/组件名/组件名.vue 的命名规范&#xff0c;就能直接使用。 注意&#xff1a;组件的文件夹…

【Vue】Vue3.0(二十六)Vue3.0中的作用域插槽

上篇文章 【Vue】Vue3.0&#xff08;二十五&#xff09;Vue3.0中的具名插槽 的概念和使用场景 &#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;Vue专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年11月20日17点30分 文章目录 概念使用场景示…

C语言第14节:字符函数和字符串函数

1. 字符分类函数 C语言中有一系列的函数是专门做字符分类的&#xff0c;也就是一个字符是属于什么类型的字符的。这些函数的使用都需要包含一个头文件是<ctype.h> <ctype.h>头文件中的字符分类函数提供了一组用于检查单个字符特性的函数。这些函数接收一个字符&a…

IDEA怎么定位java类所用maven依赖版本及引用位置

在实际开发中&#xff0c;我们可能会遇到需要搞清楚代码所用依赖版本号及引用位置的场景&#xff0c;便于排查问题&#xff0c;怎么通过IDEA实现呢&#xff1f; 可以在IDEA中打开项目&#xff0c;右键点击maven的pom.xml文件&#xff0c;或者在maven窗口下选中项目&#xff0c;…

使用vscode+expo+Android夜神模拟器运行react-native项目

1.进入夜神模拟器安装路径下的bin目录 2.输入命令&#xff0c;连接Android Studio 启动夜神模拟器后&#xff0c; 打开安装目录的bin文件夹执行下面的命令&#xff0c;只需执行一次&#xff09; nox_adb.exe connect 127.0.0.1:62001adb connect 127.0.0.1:62001 3.运行项目…

IPv6 NDP 记录

NDP&#xff08;Neighbor Discovery Protocol&#xff0c;邻居发现协议&#xff09; 是 IPv6 的一个关键协议&#xff0c;它组合了 IPv4 中的 ARP、ICMP 路由器发现和 ICMP 重定向等协议&#xff0c;并对它们作出了改进。该协议使用 ICMPv6 协议实现&#xff0c;作为 IPv6 的基…

个人全栈开发微信小程序上线了(记日记)

个人开发的全栈项目&#xff0c;《每日记鸭》微信小程序上线了&#xff01; 主要是技术栈&#xff1a;uniapp,koa2,mongodb,langchian&#xff1b; 感兴趣的小伙伴可以来捧捧场&#xff01;