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

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

版本记录

| version:
| date: 20190410
| description:
| Author: 周飞

功能介绍

防止一个方法,在方法参数值相同的情况下,短时间频繁调用

流程

利用分布式共享锁实现防止方法重复调用_第1张图片

代码

	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 = getAllParamValue(paramsName, args, parameterNames);
				//如果paramsName有对应的值,就返回字符串后再返回字符串对应的hashcode
				if (allParamValue.size() > 0) {
					sb.append(allParamValue.toString());
				}
				return  sb.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 = 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

你可能感兴趣的:(架构)