接这这一篇redis分布式锁-java实现末尾,实现aop+自定义注解 实现分布式锁

1、为什么需要 声明式的分布式锁

编程式分布式锁每次实现都要单独实现,但业务量大功能复杂时,使用编程式分布式锁无疑是痛苦的,而声明式分布式锁不同,声明式分布式锁属于无侵入式,不会影响业务逻辑的实现。

我的为什么要用:使用简单,提升开发效率

2、怎么实现

使用spring aop + 自定义注解来实现

下面来看下spring boot + 自定义注解 如何实现一个声明式的分布式锁

3、看代码

第一步、引入aop 需要 jar包
        <!-- SpringBoot AOP start -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<!-- SpringBoot AOP end -->
第二步、@EnableAspectJAutoProxy 开启AOP
@EnableAspectJAutoProxy
@SpringBootApplication
public class DemoApplication { public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
} }
第三步:自定义DistributionLock注解
import java.lang.annotation.*;

/**
* 通常 和com.example.demo.annotation.DistributionLockParam 注解配合使用,降低锁粒度
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DistributionLock { /**
* 分布式锁key
*/
String key(); /**
* 尝试获得锁的时间(单位:毫秒),默认值:5000毫秒
*
* @return 锁key过期时间
*/
long tryLockTime() default 5000; /**
* 尝试获得锁后,持有锁的时间(单位:毫秒),默认值:60000毫秒
*
* @return
*/
long holdLockTime() default 60000; /**
* 分布式锁key 的分隔符(默认 :)
*/
String delimiter() default ":"; }
第四步:自定义DistributionLockParam注解
/**
* 分布式锁参数
* 这个注解,是给DistributionLock用来控制锁粒度的
*/
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DistributionLockParam {
}
第五步:定义分布式锁AOP配置第五步:定义分布式锁AOP配置
import com.example.demo.annotation.DistributionLock;
import com.example.demo.annotation.DistributionLockParam;
import com.example.demo.entity.Resp;
import com.example.demo.redisLock.LockParam;
import com.example.demo.redisLock.RedisLock;
import lombok.extern.slf4j.Slf4j;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component; import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List; /**
* 对springboot中aop切面编程的测试
*/
//切面类
@Aspect
@Component
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
public class RedisAopAspect {
public RedisAopAspect(){
log.info("分布锁 aop init");
}
/***
* 定义切入点
*/
@Pointcut("execution(@com.example.demo.annotation.DistributionLock * *(..))")
public void pointCut(){ } @Around(value = "pointCut()")
public Object aroundMethod(ProceedingJoinPoint pjp) {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod(); /////////////////////AOP 能取得的信息 start////////////////////////
// log.info("目标方法名为:{}",pjp.getSignature().getName());
// log.info("目标方法所属类的简单类名:{}" , pjp.getSignature().getDeclaringType().getSimpleName());
// log.info("目标方法所属类的类名:{}", pjp.getSignature().getDeclaringTypeName());
// log.info("目标方法声明类型:{}" , Modifier.toString(pjp.getSignature().getModifiers()));
// log.info("目标方法返回值类型:{}" , method.getReturnType());
// //获取传入目标方法的参数
// Object[] args = pjp.getArgs();
// for (int i = 0; i < args.length; i++) {
// log.info("第{}个参数为:{}" ,(i + 1) , args[i]);
// }
// log.info("被代理的对象:{}" , pjp.getTarget());
// log.info("代理对象自己:{}" , pjp.getThis());
/////////////////////AOP 能取得的信息 end//////////////////////// //取得注解对象数据
DistributionLock lock = method.getAnnotation(DistributionLock.class);
//分布式锁实际的key
String lockKey = getRealDistributionLockKey(pjp,lock);
//创建分布式锁对象 start
LockParam lockParam = new LockParam(lockKey,lock.tryLockTime(),lock.holdLockTime());
RedisLock redisLock = new RedisLock(lockParam);
//创建分布式锁对象 end //获取锁
Boolean holdLock = redisLock.lock();
log.info("lockKey:{} holdLock:{} ",lockKey,holdLock);
if(Boolean.FALSE.equals(holdLock)){
//获取锁失败后,处理返回结果
return handleAcquireLockFailReturn(pjp);
} try {
return pjp.proceed();
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}finally {
if(redisLock!=null){
Boolean unlock = redisLock.unlock();
log.info("释放锁:unlock {}",unlock);
}
}
} /**
* 分布式锁获取失败,处理方法
* @param pjp
* @return
*/
public Object handleAcquireLockFailReturn(ProceedingJoinPoint pjp){
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
Class returnType = method.getReturnType();
//通常每个公司都有自己的统一的返回对象,Resp.class可以根据自己现有的
if(returnType.equals(Resp.class) ){
log.info("返回值类型 Resp");
return Resp.buildFail("业务处理繁忙,请稍后重试");
} throw new RuntimeException("获取锁失败");
} /**
* 加了DistributionLockParam注解参数值,按照顺序返回list
* @param pjp
* @return
*/
public List<Object> getDistributionLockParamList(ProceedingJoinPoint pjp){
ArrayList<Object> distributionLockParamList = null;
MethodSignature signature = ((MethodSignature) pjp.getSignature());
//得到拦截的方法
Method method = signature.getMethod();
//获取方法参数注解,返回二维数组是因为某些参数可能存在多个注解
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
// log.info("parameterAnnotations:{}",parameterAnnotations);
//获取全部参数
Object[] objects = pjp.getArgs();
for(int i = 0; i < parameterAnnotations.length; i++){
for(Annotation a: parameterAnnotations[i]){
if(a.annotationType() == DistributionLockParam.class){
//初始化distributionLockParamList
if(distributionLockParamList==null){
distributionLockParamList = new ArrayList();
}
//获得参数值
Object o = objects[i];
distributionLockParamList.add(o);
}
}
} return distributionLockParamList;
} /**
* 加了DistributionLockParam注解参数值,拼接成字符串
* @param pjp
* @param lock
* @return
*/
public String getDistributionLockParamStr(ProceedingJoinPoint pjp,DistributionLock lock){
List<Object> distributionLockParamList = getDistributionLockParamList(pjp);
if(distributionLockParamList!=null && distributionLockParamList.size()>0){
StringBuffer sb = new StringBuffer();
for (int i = 0; i < distributionLockParamList.size(); i++) {
Object param = distributionLockParamList.get(i);
sb.append(lock.delimiter());
sb.append(param);
}
return sb.toString();
}
return "";
} /**
* 返回分布式锁key完整的key
* @param pjp
* @param lock
* @return
*/
public String getRealDistributionLockKey(ProceedingJoinPoint pjp,DistributionLock lock){
String distributionLockParamStr = getDistributionLockParamStr(pjp,lock);
return lock.key().concat(distributionLockParamStr);
} }

LockParam和RedisLock类已经在 【redis分布式锁-java实现】文章里面有,这里就不贴出来了


Service 使用例子
import com.example.demo.entity.Resp;

public interface IOrderService {
Resp updateOrder(String orderCode, Integer userId, Integer status);
}
import com.example.demo.annotation.DistributionLock;
import com.example.demo.annotation.DistributionLockParam;
import com.example.demo.entity.Resp;
import com.example.demo.service.IOrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; @Slf4j
@Service
public class OrderServiceImpl implements IOrderService { @Override
@DistributionLock(key = "updateOrderSatus",tryLockTime = 1000)
public Resp updateOrder(@DistributionLockParam String orderCode, Integer userId, Integer status){
try {
log.info("updateOrder 处理业务 start");
Thread.sleep(1000*10);
log.info("updateOrder 处理业务 end");
} catch (InterruptedException e) {
e.printStackTrace();
}
return Resp.buildSuccess("修改订单状态成功");
} }
collection调用service
import com.example.demo.entity.Resp;
import com.example.demo.service.IOrderService;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; @Slf4j
@RestController
@RequestMapping(value = "/v1/test")
public class TestController {
@Autowired
IOrderService orderService; @ApiOperation(value = "修改订单状态")
@ApiImplicitParams({
@ApiImplicitParam(name = "orderCode", value = "订单编号", paramType = "query"),
@ApiImplicitParam(name = "userId", value = "用户ID", paramType = "query"),
@ApiImplicitParam(name = "status", value = "订单状态 1:未发货 2:已发货 3:完成", paramType = "query"),
})
@RequestMapping(value = "/updateOrderStatus", method = RequestMethod.PUT)
public Resp updateOrderStatus(@RequestParam(value = "orderCode")String orderCode,
@RequestParam(value = "userId")Integer userId,
@RequestParam(value = "status")Integer status){
log.info("updateOrderStatus reqParam:orderCode:{},userId:{},status:{}",orderCode,userId,status);
return orderService.updateOrder(orderCode,userId,status);
} }

4、浏览器swagger调用

5、下面调用一次这个接口,控制台打印的日志

Connected to the target VM, address: '127.0.0.1:63200', transport: 'socket'

  .   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.0) 2021-05-25 23:13:10.967 INFO 57853 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to default profiles: default
2021-05-25 23:13:11.993 INFO 57853 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2021-05-25 23:13:12.000 INFO 57853 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-05-25 23:13:12.000 INFO 57853 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.46]
2021-05-25 23:13:12.066 INFO 57853 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2021-05-25 23:13:12.066 INFO 57853 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1059 ms
2021-05-25 23:13:12.119 INFO 57853 --- [ main] com.example.demo.config.RedisAopAspect : 分布锁 aop init
2021-05-25 23:13:12.694 INFO 57853 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-05-25 23:13:12.695 INFO 57853 --- [ main] d.s.w.p.DocumentationPluginsBootstrapper : Context refreshed
2021-05-25 23:13:12.705 INFO 57853 --- [ main] d.s.w.p.DocumentationPluginsBootstrapper : Found 1 custom documentation plugin(s)
2021-05-25 23:13:12.715 INFO 57853 --- [ main] s.d.s.w.s.ApiListingReferenceScanner : Scanning for api listing references
2021-05-25 23:13:12.880 INFO 57853 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 2.249 seconds (JVM running for 2.777)
2021-05-25 23:13:12.882 INFO 57853 --- [ main] o.s.b.a.ApplicationAvailabilityBean : Application availability state LivenessState changed to CORRECT
2021-05-25 23:13:12.883 INFO 57853 --- [ main] o.s.b.a.ApplicationAvailabilityBean : Application availability state ReadinessState changed to ACCEPTING_TRAFFIC
2021-05-25 23:19:58.582 INFO 57853 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-05-25 23:19:58.582 INFO 57853 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2021-05-25 23:19:58.583 INFO 57853 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
2021-05-25 23:20:11.970 INFO 57853 --- [nio-8080-exec-2] c.e.demo.controller.TestController : updateOrderStatus reqParam:orderCode:1,userId:2,status:3
2021-05-25 23:20:11.996 INFO 57853 --- [nio-8080-exec-2] com.example.demo.config.RedisAopAspect : lockKey:updateOrderSatus:1 holdLock:true
2021-05-25 23:20:12.000 INFO 57853 --- [nio-8080-exec-2] c.e.demo.service.impl.OrderServiceImpl : updateOrder 处理业务 start
2021-05-25 23:20:22.005 INFO 57853 --- [nio-8080-exec-2] c.e.demo.service.impl.OrderServiceImpl : updateOrder 处理业务 end
2021-05-25 23:20:22.007 INFO 57853 --- [nio-8080-exec-2] com.example.demo.config.RedisAopAspect : 释放锁:unlock true

spring boot启动类:com.example.demo.DemoApplication

swagger 地址:http://127.0.0.1:8080/swagger-ui.html

其他:

如果声明式的分布式锁想实现可重入机制,可以把new RedisLock(lockParam),替换成这篇文章的 redis分布式锁-可重入锁 就能实现了

代码我已经上传到gitee上了,大家可以下载玩一下,记得star一下

gitee 代码传送门

redis分布式锁-spring boot aop+自定义注解实现分布式锁的更多相关文章

  1. spring boot aop 自定义注解 实现 日志检验 权限过滤

    核心代码: package com.tran.demo.aspect; import java.lang.reflect.Method; import java.time.LocalDateTime; ...

  2. Spring Boot中自定义注解+AOP实现主备库切换

    摘要: 本篇文章的场景是做调度中心和监控中心时的需求,后端使用TDDL实现分表分库,需求:实现关键业务的查询监控,当用Mybatis查询数据时需要从主库切换到备库或者直接连到备库上查询,从而减小主库的 ...

  3. spring boot通过自定义注解和AOP拦截指定的请求

    一 准备工作 1.1 添加依赖 通过spring boot创建好工程后,添加如下依赖,不然工程中无法使用切面的注解,就无法对制定的方法进行拦截 <dependency> <group ...

  4. Spring Boot Web 自定义注解篇(注解很简单很好用)

    自从spring 4.0 开放以后,可以添加很多新特性的注解了.使用系统定义好的注解可以大大方便的提高开发的效率. 下面我贴一段代码来讲解注解: 通过小小的注解我们支持了以下功能: 使 spring. ...

  5. Spring Boot实现自定义注解

    在Spring Boot项目中可以使用AOP实现自定义注解,从而实现统一.侵入性小的自定义功能. 实现自定义注解的过程也比较简单,只需要3步,下面实现一个统一打印日志的自定义注解: 1. 引入AOP依 ...

  6. 使用AOP+自定义注解完成spring boot的接口权限校验

    记使用AOP+自定义注解完成接口的权限校验,代码如下: pom文件添加所需依赖: 1 <dependency> 2 <groupId>org.aspectj</group ...

  7. ssm+redis 如何更简洁的利用自定义注解+AOP实现redis缓存

    基于 ssm + maven + redis 使用自定义注解 利用aop基于AspectJ方式 实现redis缓存 如何能更简洁的利用aop实现redis缓存,话不多说,上demo 需求: 数据查询时 ...

  8. 利用Spring AOP自定义注解解决日志和签名校验

    转载:http://www.cnblogs.com/shipengzhi/articles/2716004.html 一.需解决的问题 部分API有签名参数(signature),Passport首先 ...

  9. ssm+redis整合(通过aop自定义注解方式)

    此方案借助aop自定义注解来创建redis缓存机制. 1.创建自定义注解类 package com.tp.soft.common.util; import java.lang.annotation.E ...

随机推荐

  1. 什么是事务?事务的四个特性(ACID)?并发事务带来哪些问题?事务隔离级别都有哪些?事务的传播特性

    什么是事务? 事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消.也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做. 事物的四个 ...

  2. Hadoop学习笔记—HDFS

    目录 搭建安装 三个核心组件 安装 配置环境变量 配置各上述三组件守护进程的相关属性 启停 监控和性能 Hadoop Rack Awareness yarn的NodeManagers监控 命令 hdf ...

  3. Codeforces-121C(逆康托展开)

    题目大意: 给你两个数n,k求n的全排列的第k小,有多少满足如下条件的数: 首先定义一个幸运数字:只由4和7构成 对于排列p[i]满足i和p[i]都是幸运数字 思路: 对于n,k<=1e9 一眼 ...

  4. 当初自学C++时的笔记记录

    编辑:刘风琛 最初编写日期:2020年4月11日下午 最新更新日期:2020年9月20日上午 标注: 从笔记开始截止到程序第四章"程序流程结构",使用Joplin编写,其余部分为T ...

  5. 学习C#第二天

    变量 变量是什么? 在数学中,我们对变量的概念有一定的了解和认识,如y=x^2,其中,x,y都是变量. 定义 一个变量就是存储区(内存)中的一个存储单元 变量的声明及初始化 使用变量的步骤 声明一个变 ...

  6. 全网最详细的Linux命令系列-cd命令

    Linux cd 命令可以说是Linux中最基本的命令语句,其他的命令语句要进行操作,都是建立在使用 cd 命令上的. 所以,学习Linux 常用命令,首先就要学好 cd 命令的使用方法技巧. 命令格 ...

  7. 2020牛客NOIP赛前集训营-普及组(第二场)A-面试

    面 试 面试 面试 题目描述 牛牛内推了好多人去牛客网参加面试,面试总共分四轮,每轮的面试官都会对面试者的发挥进行评分.评分有 A B C D 四种.如果面试者在四轮中有一次发挥被评为 D,或者两次发 ...

  8. 使用 EPPlus 封装的 excel 表格导入功能 (.net core c#)

    使用 EPPlus 封装的 excel 表格导入功能 前言 最近做系统的时候有很多 excel导入 的功能,以前我前后端都做的时候是在前端解析,然后再做个批量插入的接口 我觉着这样挺好的,后端部分可以 ...

  9. IDEA xml 注解快捷键

    注释:CTRL + SHIFT + / 撤销注释:CTRL + SHIFT + \

  10. JAVAEE_Servlet_01_Servlet基础概念

    Servlet 基础概念 Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间 ...