SpringBoot分布式锁自定义注解处理幂等性

news2024/12/28 18:33:51

SpringBoot分布式锁+自定义注解处理幂等性

注解简介

注解(Annotation)是Java SE 5.0 版本开始引入的概念,它是对 Java 源代码的说明,是一种元数据(描述数据的数据)。

Java中的注解主要分为以下三类:

JDK的注解
第三方的注解
自定义注解

JDK注解

Java内置注解
    @Override (标记重写方法)
    @Deprecated (标记过时)
    @SuppressWarnings (忽略警告)
元注解 (注解的注解)
    @Target (注解的作用目标)
    @Retention (注解的生命周期)
    @Document (注解是否被包含在JavaDoc中)
    @Inherited (是否允许子类集成该注解)
@Target

用于描述注解的使用范围,有一个枚举ElementType来指定,具体如下:

Target类型描述
ElementType.TYPE应用于类、接口(包括注解类型)、枚举
ElementType.FIELD应用于属性(包括枚举中的常量)
ElementType.METHOD应用于方法
ElementType.PARAMETER应用于方法的形参
ElementType.CONSTRUCTOR应用于构造函数
ElementType.LOCAL_VARIABLE应用于局部变量
ElementType.ANNOTATION_TYPE应用于注解类型
ElementType.PACKAGE应用于包
ElementType.TYPE_PARAMETER应用于类型变量
ElementType.TYPE_USE应用于任何使用类型的语句中(例如声明语句、泛型和强制转换语句中的类型)
@Retention

表示需要在什么级别保存该注释信息,用于描述注解的生命周期,也是一个枚举RetentionPoicy来决定的

取值含义
RetentionPolicy.SOURCE源码中保留,编译期可以处理
RetentionPolicy.CLASSClass文件中保留,Class加载时可以处理
RetentionPolicy.RUNTIME运行时保留,运行中可以处理

一般填RetentionPoicy.RUNTIME即可

@Documented

如果用javadoc生成文档时,想把注解也生成文档,就带这个。

@Inherited

@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。注意,@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。

自定义注解

使用JDK中一些元注解,@Target,@Retention,@Document,@Inherited来修饰注解。具体格式如下:
在这里插入图片描述

自定义注解实例:

import org.springblade.core.redis.lock.LockType;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁注解处理幂等性
 * 分布式锁注解,Redisson,支持的锁的种类有很多,适合注解形式的只有重入锁、公平锁
 *
 * <p>
 * 1. 可重入锁(Reentrant Lock)
 * 2. 公平锁(Fair Lock)
 * 3. 联锁(MultiLock)
 * 4. 红锁(RedLock)
 * 5. 读写锁(ReadWriteLock)
 * </p>
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface RedisIdempotentLock {

    /**
     * 分布式锁的 key,必须:请保持唯一性
     *
     * @return key
     */
    String prefix() default "";

    /**
     * 分布式锁参数,可选,支持 spring el # 读取方法参数和 @ 读取 spring bean
     *
     * @return param
     */
    String param() default "";

    /**
     * 使用用户id作为新增等接口作为唯一key,处理幂等
     *
     * @return param
     */
    boolean isUserId() default false;
    /**
     * 等待锁超时时间,默认30
     *
     * @return int
     */
    long waitTime() default 30;

    /**
     * 自动解锁时间,自动解锁时间一定得大于方法执行时间,否则会导致锁提前释放,默认-1
     *
     * @return int
     */
    long leaseTime() default -1;

    /**
     * 时间单温,默认为秒
     *
     * @return 时间单位
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;

    /**
     * 默认公平锁
     *
     * @return LockType
     */
    LockType type() default LockType.FAIR;

}


AOP 切面通用分布式锁+自定义注解处理幂等性;

import com.zhkj.ims.anotation.RedisIdempotentLock;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.redis.lock.LockType;
import org.springblade.core.redis.lock.RedisLockClient;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.spel.BladeExpressionEvaluator;
import org.springblade.core.tool.utils.CharPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * redis 分布式锁处理幂等性
 */
@Aspect
@Component
public class RedisIdempotentLockAspect implements ApplicationContextAware {

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisIdempotentLockAspect.class);

    /**
     * 表达式处理
     */
    private static final BladeExpressionEvaluator EVALUATOR = new BladeExpressionEvaluator();
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedisLockClient redisLockClient;
    @Autowired
    private ApplicationContext applicationContext;

    private static final String DEFAULT_SUPER_PREFIX = "idempotence";

    /**
     * AOP 环切 注解 @RedisIdempotentLock
     */
    @Around(value = "@annotation(redisIdempotentLock)")
    public Object aroundRedisLock(ProceedingJoinPoint point, RedisIdempotentLock redisIdempotentLock) throws Throwable {
        String prefix = redisIdempotentLock.prefix();
        Class clazz = point.getTarget().getClass();
        String methodName = point.getSignature().getName();
        String lockName;
        String fullClassName = clazz.getName();
        if (StringUtils.hasText(fullClassName)) {
            String[] splits = fullClassName.split("\\.");
            String className = splits[splits.length - 1];
            lockName = className + CharPool.COLON + methodName;
        } else {
            lockName = methodName;
        }
        lockName = StringUtils.hasText(prefix) ? DEFAULT_SUPER_PREFIX + CharPool.COLON + prefix + CharPool.COLON + lockName : DEFAULT_SUPER_PREFIX + CharPool.COLON + lockName;
        String lockParam = redisIdempotentLock.param();
        String lockKey;
        if (StringUtil.isNotBlank(lockParam)) {
            // 解析表达式
            String evalAsText = evalLockParam(point, lockParam);
            lockKey = lockName + CharPool.COLON + evalAsText;
            if (redisIdempotentLock.isUserId()) {
                lockKey = lockKey + CharPool.COLON + AuthUtil.getUserId();
            }
        } else {
            if (redisIdempotentLock.isUserId()) {
                lockKey = lockName + CharPool.COLON + AuthUtil.getUserId();
            } else {
                lockKey = lockName;
            }
        }
        LockType lockType = redisIdempotentLock.type();
        long waitTime = redisIdempotentLock.waitTime();
        long leaseTime = redisIdempotentLock.leaseTime();
        TimeUnit timeUnit = redisIdempotentLock.timeUnit();
        Object result;
        boolean release = false;
        if (existKey(lockKey)) {
            throw new ServiceException("操作进行中,请稍后重试!");
        }
        try {
            boolean tryLock = redisLockClient.tryLock(lockKey, lockType, waitTime, leaseTime, timeUnit);
            if (tryLock) {
                release = true;
                result = point.proceed();
            } else {
                throw new ServiceException("操作进行中,请稍后重试!");
            }
        } catch (Exception e) {
            LOGGER.info("方法处理异常:{}", e.getMessage());
            throw e;
        } finally {
            if (release && existKey(lockKey)) {
                LOGGER.info("释放锁key:{}", lockKey);
                redisLockClient.unLock(lockKey, lockType);
            }
        }
        return result;
    }

    /**
     * 计算参数表达式
     *
     * @param point     ProceedingJoinPoint
     * @param lockParam lockParam
     * @return 结果
     */
    private String evalLockParam(ProceedingJoinPoint point, String lockParam) {
        MethodSignature ms = (MethodSignature) point.getSignature();
        Method method = ms.getMethod();
        Object[] args = point.getArgs();
        Object target = point.getTarget();
        Class<?> targetClass = target.getClass();
        EvaluationContext context = EVALUATOR.createContext(method, args, target, targetClass, applicationContext);
        AnnotatedElementKey elementKey = new AnnotatedElementKey(method, targetClass);
        return EVALUATOR.evalAsText(lockParam, elementKey, context);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    private boolean existKey(String lockKey) {
        Assert.hasText(lockKey, "lockKey must not null.");
        return redisTemplate.hasKey(lockKey);
    }
}

​ 具体使用

@RestController
public class TestController {

   @Autowired
   private JdbcConfig jdbcConfig;

   @RedisIdempotentLock(param = "#id")
   @PostMapping("/hello")
   public String Hello(Long id) {
       return jdbcConfig.getUrl() + "  " + jdbcConfig.getDriver() + " " + jdbcConfig.getUser() + " " + jdbcConfig.getPassword();
   }
}

{

@Autowired
private JdbcConfig jdbcConfig;

@RedisIdempotentLock(param = “#id”)
@PostMapping(“/hello”)
public String Hello(Long id) {
return jdbcConfig.getUrl() + " " + jdbcConfig.getDriver() + " " + jdbcConfig.getUser() + " " + jdbcConfig.getPassword();
}
}

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

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

相关文章

HTML5 和 CSS3 提高

一、HTML5 的新特性 HTML5 的新增特性主要是针对于以前的不足&#xff0c;增加了一些新的标签、新的表单和新的表单属性等。这些新特性都有兼容性问题&#xff0c;基本是 IE9 以上版本的浏览器才支持&#xff0c;如果不考虑兼容性问题&#xff0c;可以大量使用这些新特性。 声明…

账号微服务短信验证码发送工具单元测试

账号微服务短信验证码发送工具单元测试 注意sms的 app-code #----------sms短信配置-------------- sms:app-code: dd7829bedfaf4373875aa91abba82523template-id: JM1000372package net.xdclass.config;import org.springframework.context.annotation.Bean; import org.spri…

人工智能|推荐系统——搜索引擎广告

原文题目 Dark sides of artificial intelligence: The dangers of automated decision-making in search engine advertising(JASIST,2023) 人工智能的阴暗面:搜索引擎广告自动决策的危险 摘要 随着人工智能应用的日益广泛,搜索引擎供应商越来越多地要求广告商使用基于机…

Clickhouse-表引擎探索之MergeTree

引言 前文曾说过&#xff0c;Clickhouse是一个强大的数据库Clickhouse-一个潜力无限的大数据分析数据库系统 其中一个强大的点就在于支持各类表引擎以用于不同的业务场景。 MergeTree MergeTree系列的引擎被设计用于插入极大量的数据到一张表当中。数据可以以数据片段的形式一…

OpenHarmony实战:Vmware虚拟机和Ubuntu安装

避坑指南 1. 虚拟机命名、用户名称、路径不能有汉字 名称或者路径有汉字&#xff0c;导致输入失败或者安装失败 2. 虚拟机处理器内核总数&#xff08;处理器数量 X 每个处理器的内核数量&#xff09;不得超过电脑逻辑处理器总个数 太少时&#xff0c;下载代码和编译非常缓慢…

YOLOv9解读

论文地址&#xff1a;https://arxiv.org/abs/2402.13616 Github地址&#xff1a;https://github.com/WongKinYiu/yolov9 一、引言 作者认为当前深度学习方法忽略了一个事实&#xff0c;即当输入数据经过逐层特征提取和空间变换时&#xff0c;大量信息将会丢失。本文基于深入研…

ASP.NET制作试卷(单选+多选)

需求&#xff1a; 1.包含单选题、多选题。 2.所有题做完再提交。 3.提示错误、统计分数&#xff08;提交后&#xff09;。 项目结构&#xff1a; 效果展示&#xff1a; 效果展示&#xff08;视频&#xff09;&#xff1a; ASP.NET练习1效果 index.aspx代码&#xff1a; &l…

如何在 Mac 上打开、编辑、复制、移动或删除存储在 Windows NTFS 格式 USB 驱动器上的文件 Tuxera NTFS for Mac使用教程

当您获得一台新 Mac 时&#xff0c;它只能读取 Windows NTFS 格式的 USB 驱动器。要将文件添加、保存或写入您的 Mac&#xff0c;您需要一个附加的 NTFS 驱动程序。Tuxera 他可以帮忙实现这一功能&#xff01; Tuxera可以轻松转换驱动器&#xff1a;无论使用Windows PC还是Mac&…

期货开户要找到适合自己的系统

物有一个生物圈&#xff0c;大鱼吃小鱼&#xff0c;小鱼吃虾。在期货市场这条生物圈里面&#xff0c;大部分人就是期货市场的虾子&#xff0c;是被吃的&#xff0c;所以必须成长起来&#xff0c;往更高一层走&#xff0c;到可以吃虾子的时候&#xff0c;就是挣钱的时候。学习不…

深度学习pytorch——正则化(持续更新)

由于Sigmoid函数在两边存在梯度趋于零的特性&#xff0c;这种特性会使梯度长久得不到更新&#xff0c;造成梯度离散的现象&#xff0c;如何处理这一种现象&#xff1f;压缩数据使数据位于Sigmoid梯度不趋于0区间&#xff0c;即批量正则化&#xff08;Batch Norm &#xff09;&a…

钉钉 AI 升级多种功能;智谱AI PC智能助手发布;百度回应与苹果合作

▶ 钉钉 AI 升级上线多种功能 3 月 28 日&#xff0c;钉钉 AI 助理升级。升级后上线了图片理解、文档速读、工作流等产品能力&#xff0c;率先探索多模态、长文本与 RPA 技术在 AI 应用的落地。 基于阿里通义千问大模型&#xff0c;升级后的钉钉 AI 助理可以做到&#xff1a; …

如何撰写研究论文

SEVENTYFOUR/SHUTTERSTOCK 即使对于有经验的作家来说&#xff0c;将数月或数年的研究浓缩到几页纸中也是一项艰巨的任务。作者需要在令人信服地解决他们的科学问题和详细地呈现他们的结果之间找到最佳平衡点&#xff0c;以至于丢失了关键信息。他们必须简明扼要地描述他们的方…

github拉取的项目添加至自己的仓库

想把GitHub的开源项目拉到本地进行二开&#xff0c;研究了一下上传到gitee的步骤&#xff1a; 步骤 gitee新建仓库&#xff0c;仓库名与本地文件夹的名称一致&#xff0c;建好后gitee的页面也会有显示git命令 打开项目目录&#xff0c;右键打开git bash&#xff08;或者在git…

蓝桥集训之松散子序列

蓝桥集训之松散子序列 核心思想&#xff1a;状态机dp 每个点两种状态 f[i,0] max(f[i-1,0] , f[i-1,1]) f[i,1] f[i-1,0] w; #include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N 1000010;int f[N][2];int…

LLMs之Mistral:Mistral 7B v0.2的简介、安装和使用方法、案例应用之详细攻略

LLMs之Mistral&#xff1a;Mistral 7B v0.2的简介、安装和使用方法、案例应用之详细攻略 导读&#xff1a;Mistral AI首个7B模型发布于2023年9月&#xff0c;在基准测试中超越Llama 2 13B&#xff0c;一下子声名大振。Mistral 7B v0.2对应的指令调优版本Mistral-7B-Instruct-v0…

MySQL驱动Add Batch优化实现

MySQL 驱动 Add Batch 优化实现 MySQL 驱动会在 JDBC URL 添加 rewriteBatchedStatements 参数时&#xff0c;对 batch 操作进行优化。本文测试各种参数组合的行为&#xff0c;并结合驱动代码简单分析。 batch参数组合行为 useServerPrepStmts 参数 PreparedStatement psmt…

C# wpf 实现底部嵌入HwndHost

WPF Hwnd窗口互操作系列 第一章 嵌入Hwnd窗口 第二章 嵌入WinForm控件 第三章 嵌入WPF控件 第四章 底部嵌入HwndHost&#xff08;本章&#xff09; 文章目录 WPF Hwnd窗口互操作系列前言一、如何实现&#xff1f;1、底部创建窗口&#xff08;1&#xff09;、创建透明窗口&…

[flink 实时流基础]源算子和转换算子

文章目录 1. 源算子 Source1. 从集合读2. 从文件读取3. 从 socket 读取4. 从 kafka 读取5. 从数据生成器读取数据 2. 转换算子基本转换算子&#xff08;map/ filter/ flatMap&#xff09; 1. 源算子 Source Flink可以从各种来源获取数据&#xff0c;然后构建DataStream进行转换…

ios应用内支付

用uniapp开发iOS应用内支付 准备前端代码服务器端处理如果iOS支付遇到问题实在解决不了&#xff0c;可以联系我帮忙解决&#xff0c;前端后端都可以解决&#xff08;添加的时候一定要备注咨询iOS支付问题&#xff09; 准备前端代码 获取支付通道 (uni.getProvider) uni.getPr…

怎么更新sd-webui AUTOMATIC1111/stable-diffusion-webui ?

整个工程依靠脚本起来的&#xff1a; 可直接到stable-diffusion-webui子目录执行&#xff1a; git pull更新代码完毕后&#xff0c;删除venv的虚拟环境。 然后再次执行webui.sh&#xff0c;这样会自动重新启动stable-diffusion-webui.