利用分布式共享锁实现防止方法重复调用

利用分布式共享锁实现防止方法重复调用 版本记录 | 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()+"测试结束"); }

方案优点
  1. 解决表单防重复提交
  2. 将防方法重复执行的功能和业务逻辑区分开发
  3. 可以解决计划任务中方法重复执行的问题。
方案缺点
【利用分布式共享锁实现防止方法重复调用】对实现方案的缺点进行说明,以便后续改进
待改善点
对待完善点进行列举,以便后续改进
其他说明 改进方案
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 {}; }

对比前一个方案优点
  1. 代码更简洁
  2. 提供自定义参数名称来定义key

    推荐阅读