利用分布式共享锁实现防止方法重复调用
版本记录 | version:
| date: 20190410
| description:
| Author: 周飞
功能介绍
防止一个方法,在方法参数值相同的情况下,短时间频繁调用流程
文章图片
代码
package com.lolaage.common.annotations;
import java.lang.annotation.*;
/**
* @Author feizhou
* @Description 防止同一个方法被频繁执行(是否需要频繁执行看参数params是否不一样)
* @Date 19:35 2019/4/9
* @Param
* @return
**/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SameMethodFrequentlyRun {
/**
* @Author feizhou
* @Description 当方法的参数是实体对象,对象必须对象重写equal和hashcode方法
**/
String params()default "";
String description()default "";
/**
* @Author feizhou
* @Description
**/
long milliseconds()default 30000L;
}
package com.lolaage.common.aop;
import com.lolaage.base.po.JsonModel;
import com.lolaage.common.annotations.SameMethodFrequentlyRun;
import com.lolaage.helper.util.RedisLockTemplate;
import com.lolaage.util.StringUtil;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.stereotype.Component;
import java.lang.reflect.Method;
/**
* @Author feizhou
* @Description防止同一个方法被频繁执行AOP(是否需要频繁执行看参数params是否不一样)
**/@Aspect
@Component
public class SameMethodFrequentlyRunAop {
private static Logger logger = Logger.getLogger(SameMethodFrequentlyRunAop.class);
// 配置接入点,即为所要记录的action操作目录
@Pointcut("execution(* com.lolaage.helper.web.controller..*.*(..))")
private void controllerAspect() { } @Around("controllerAspect()")
public Object around(ProceedingJoinPoint pjp) {
Object returnObj=null;
StringBuilder sb=new StringBuilder();
// 拦截的实体类,就是当前正在执行的controller
Object target = pjp.getTarget();
//获取全类名
String className=target.getClass().getName();
// 拦截的方法名称。当前正在执行的方法
String methodName = pjp.getSignature().getName();
// 拦截的方法参数
Object[] args = pjp.getArgs();
// 拦截的放参数类型
Signature sig = pjp.getSignature();
MethodSignature msig = (MethodSignature) sig ;
Class[] parameterTypes = msig.getMethod().getParameterTypes();
sb.append(className);
for (Object o : args) {
if(o==null){
continue;
}
int i = o.hashCode();
sb.append(":");
sb.append(i);
}
// 获得被拦截的方法
Method method = null;
try {
method = target.getClass().getMethod(methodName, parameterTypes);
SameMethodFrequentlyRun sameMethodFrequentlyRun = method.getAnnotation(SameMethodFrequentlyRun.class);
if (sameMethodFrequentlyRun != null) {
String description = sameMethodFrequentlyRun.description();
String params = sameMethodFrequentlyRun.params();
if(StringUtil.isEmpty(params)){
params=sb.toString();
}
long milliseconds = sameMethodFrequentlyRun.milliseconds();
Boolean isGetLock = RedisLockTemplate.distributedLock_v2(params, description, milliseconds, false);
if(!isGetLock){
//提示不要重复操作
JsonModel result = new JsonModel();
return result.setErrCode(5004);
}
}
} catch (NoSuchMethodException e) {
logger.error("分布式防重复操作异常:AOP只会拦截public方法,非public会报异常,如果你要将你的方法加入到aop拦截中,请修改方法的修饰符:"+e.getMessage());
}
try {
returnObj = pjp.proceed();
} catch (Throwable e) {
logger.error("分布式防重复操作异常Throwable:"+e.getMessage());
e.printStackTrace();
}
return returnObj;
}}
/**
* 分布式锁压力测试,和防重复测试
* @return
*/
@SameMethodFrequentlyRun(description="查询操作日志",milliseconds = 10000L)
@RequestMapping("/pressureLock")
public void pressureLock(String key,QuitParam quitParam) {System.out.println(this.hashCode()+"---"+Thread.currentThread().getName()+":测试开始");
System.out.println(this.hashCode()+"---"+Thread.currentThread().getName()+"测试结束");
}
方案优点
- 解决表单防重复提交
- 将防方法重复执行的功能和业务逻辑区分开发
- 可以解决计划任务中方法重复执行的问题。
【利用分布式共享锁实现防止方法重复调用】对实现方案的缺点进行说明,以便后续改进待改善点
对待完善点进行列举,以便后续改进其他说明 改进方案
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* @Author feizhou
* @Description 防止同一个方法被频繁执行AOP(是否需要频繁执行看参数params是否不一样)
**/ @Aspect
@Component
public class SameMethodFrequentlyRunAop {
private static Logger logger = Logger.getLogger(SameMethodFrequentlyRunAop.class);
// 配置接入点,即为所要记录的action操作目录
@Pointcut("execution(* com.lolaage.helper.web.controller..*.*(..))")
private void controllerAspect() {}@Around("controllerAspect()")
public Object around(ProceedingJoinPoint pjp) {
Object returnObj = null;
// 拦截的放参数类型
Signature sig = pjp.getSignature();
MethodSignature msig = (MethodSignature) sig;
Method method = msig.getMethod();
//获取方法注解
SameMethodFrequentlyRun sameMethodFrequentlyRun = method.getAnnotation(SameMethodFrequentlyRun.class);
if (sameMethodFrequentlyRun != null) {
String description = sameMethodFrequentlyRun.description();
String params = sameMethodFrequentlyRun.params();
String[] paramsName = sameMethodFrequentlyRun.paramsName();
// 拦截的实体类,就是当前正在执行的controller
Object target = pjp.getTarget();
//获取全类名
String className = target.getClass().getName();
//方法参数值数组
Object[] args = pjp.getArgs();
//获取参数名称数组
String[] parameterNames = msig.getParameterNames();
//方法名
String methodName = method.getName();
//构建key
int hashCode = buildParamsToHashCode(args, className,methodName, params, paramsName, parameterNames);
String key = String.valueOf(hashCode);
long milliseconds = sameMethodFrequentlyRun.milliseconds();
Boolean isGetLock = RedisLockTemplate.distributedLock_v2(key, description, milliseconds, false);
if (!isGetLock) {
//提示不要重复操作
JsonModel result = new JsonModel();
return result.setErrCode(5004);
}
}try {
returnObj = pjp.proceed();
} catch (Throwable e) {
logger.error("分布式防重复操作异常Throwable:" + e.getMessage());
e.printStackTrace();
}
return returnObj;
}/**
* @return void
* @Author feizhou
* @Description 构建params, 并转化为hashcode
* @Date 10:27 2019/8/27
* @Param
**/
private int buildParamsToHashCode(Object[] args, String className,String methodName, String params, String[] paramsName, String[] parameterNames) {StringBuilder sb = new StringBuilder();
sb.append(className);
sb.append(methodName);
//只要params或者paramsName存在
if (!"".equalsIgnoreCase(params)||paramsName.length > 0) {
sb.append(params);
List allParamValue = https://www.it610.com/article/getAllParamValue(paramsName, args, parameterNames);
//如果paramsName有对应的值,就返回字符串后再返回字符串对应的hashcode
if (allParamValue.size()> 0) {
sb.append(allParamValue.toString());
}
returnsb.toString().hashCode();
}//params=null 且paramsName==null或者paramsName没有对应的值
//返回参数对应的字符串的hashcode
for (Object o : args) {
if (o == null) {
continue;
}
sb.append(":");
sb.append(o.toString());
}
return sb.toString().hashCode();
}/**
* @return java.util.List
* @Author feizhou
* @Description 获取参数的值
* @Date 11:04 2019/8/27
* @Param paramsName
* @Param args
* @Param parameterNames
**/
private List getAllParamValue(String[] paramsName, Object[] args, String[] parameterNames) {List newArgs = new ArrayList<>();
for (String paramName : paramsName) {
//获取参数名称对应的数组下标
int paramaIndex = getParamaIndex(paramName, parameterNames);
//如果没有找打元素就跳过
if (paramaIndex == -1) {
continue;
}
//获取参数下标对应的值
Object paramaValue = https://www.it610.com/article/getParamaValue(paramaIndex, args);
if(paramaValue!=null){
newArgs.add(paramaValue.toString());
}
}
return newArgs;
}/**
* @return java.lang.String
* @Author feizhou
* @Description 获取参数下标对应的值
* @Date 10:48 2019/8/27
* @Param paramName
* @Param args
**/
private Object getParamaValue(int paramaIndex, Object[] args) {
Object obj = args[paramaIndex];
if(obj==null){
return null;
}
//判断数组情况
Class clazz = obj.getClass();
if (clazz.isArray()) { //这个对象是数组,转list
int length = Array.getLength(obj);
//获取数组长度
List list=new ArrayList<>();
for (int i =0 ;
i
import java.lang.annotation.*;
/**
* @Author feizhou
* @Description 防止同一个方法被频繁执行
* 所有请求的参数都必须重写toString方法,也就是说凡是带对象的参数都要重写toString方法
* @Date 19:35 2019/4/9
* @Param
* @return
**/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SameMethodFrequentlyRun {
/**
* @Author feizhou
* @Description 可以作为定义key的前缀
**/
String params()default "";
/**
* @Author feizhou
* @Description 描述
**/
String description()default "";
/**
* @Author feizhou
* @Description 单位毫秒,30秒
**/
long milliseconds()default 30000L;
/**
* @Author feizhou
* @Description 根据传入的参数名称来定义key.可以和params一起拼接使用
**/
String[] paramsName() default {};
}
对比前一个方案优点
- 代码更简洁
- 提供自定义参数名称来定义key
推荐阅读
- 架构|架构师之路(二)程序员眼里的架构师
- 程序员|DevEcoStudio的及其传感器的使用,闭关在家37天“吃透”这份345页PDF
- java|阿里工作8年,肝到P8就剩这份学习笔记了,已助朋友拿到10个Offer