Title

java防止频繁请求、重复提交(防抖动)

在客户端网络慢或者服务器响应慢时,用户有时是会频繁刷新页面或重复提交表单的,这样是会给服务器造成不小的负担的,同时在添加数据时有可能造成不必要的麻烦。

自定义注解

/**
 * 防止重复请求
 */
@Target(ElementType.METHOD) // 作用到方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时有效
public @interface NoRepeatSubmit {
 
    //名称,如果不给就是要默认的
    String name() default "name";
}

使用AOP实现该注解

/**
 * AOP实现防抖动
 */
@Aspect
@Component
@Slf4j
public class NoRepeatSubmitAop {
 
    @Autowired
    private RedisService redisService;
 
    /**
     * 切入点
     */
    // 直接作用在@RequestMapping注解上
    // @Pointcut("@within(org.springframework.web.bind.annotation.RequestMapping)")
    @Pointcut("@annotation(com.qwt.part_time_admin_api.common.validation.NoRepeatSubmit)")
    public void pt() {
    }
 
    @Around("pt()")
    public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        String sessionId = attributes.getSessionId();
        assert attributes != null;
        HttpServletRequest request = attributes.getRequest();
        //这里是唯一标识 根据情况而定
        String key = sessionId + "-" + request.getServletPath();
        // 如果缓存中有这个url视为重复提交
        if (!redisService.hasKey(key)) {
            //通过,执行下一步
            Object o = joinPoint.proceed();
            //然后存入redis 并且设置100毫秒倒计时
            redisService.setCacheObject(key, 0, 100, TimeUnit.MILLISECONDS);
            //返回结果
            return o;
        } else {
            // 返回类型不为Result
            Signature signature = joinPoint.getSignature();
            if (signature instanceof MethodSignature) {
                MethodSignature methodSignature = (MethodSignature) signature;
                // 被切的方法
                Method method = methodSignature.getMethod();
                // 返回类型
                Class<?> methodReturnType = method.getReturnType();
                if (!Result.class.getName().equals(methodReturnType.getName())) {
                    // 实例化
                    Object o = methodReturnType.newInstance();
                    return o;
                }
            }
            return Result.failed(400, "请勿重复提交或者操作过于频繁!");
        } 
    }
}

RedisService配置

package com.qwt.part_time_admin_api.service;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
 
import java.util.*;
import java.util.concurrent.TimeUnit;
 
 
/**
* redis配置
*/
@Component
public class RedisService {
 
  @Autowired
  public RedisTemplate redisTemplate;
   
  /**
   * 缓存基本的对象,Integer、String、实体类等
   *
   * @param key   缓存的键值
   * @param value 缓存的值
   * @return 缓存的对象
   */
  public <T> ValueOperations<String, T> setCacheObject(String key, T value) {
      ValueOperations<String, T> operation = redisTemplate.opsForValue();
      operation.set(key, value);
      return operation;
  }
   
  /**
   * 缓存基本的对象,Integer、String、实体类等
   *
   * @param key      缓存的键值
   * @param value    缓存的值
   * @param timeout  时间
   * @param timeUnit 时间颗粒度
   * @return 缓存的对象
   */
  public <T> ValueOperations<String, T> setCacheObject(String key, T value, Integer timeout, TimeUnit timeUnit) {
      ValueOperations<String, T> operation = redisTemplate.opsForValue();
      operation.set(key, value, timeout, timeUnit);
      return operation;
  }
   
  /**
   * 获得缓存的基本对象。
   *
   * @param key 缓存键值
   * @return 缓存键值对应的数据
   */
  public <T> T getCacheObject(String key) {
      ValueOperations<String, T> operation = redisTemplate.opsForValue();
      return operation.get(key);
  }
   
  /**
   * 删除单个对象
   *
   * @param key
   */
  public void deleteObject(String key) {
      redisTemplate.delete(key);
  }
   
  /**
   * 删除集合对象
   *
   * @param collection
   */
  public void deleteObject(Collection collection) {
      redisTemplate.delete(collection);
  }
   
  /**
   * 缓存List数据
   *
   * @param key      缓存的键值
   * @param dataList 待缓存的List数据
   * @return 缓存的对象
   */
  public <T> ListOperations<String, T> setCacheList(String key, List<T> dataList) {
      ListOperations listOperation = redisTemplate.opsForList();
      if (null != dataList) {
          int size = dataList.size();
          for (int i = 0; i < size; i++) {
              listOperation.leftPush(key, dataList.get(i));
          }
      }
      return listOperation;
  }
   
  /**
   * 获得缓存的list对象
   *
   * @param key 缓存的键值
   * @return 缓存键值对应的数据
   */
  public <T> List<T> getCacheList(String key) {
      List<T> dataList = new ArrayList<>();
      ListOperations<String, T> listOperation = redisTemplate.opsForList();
      Long size = listOperation.size(key);
   
      for (int i = 0; i < size; i++) {
          dataList.add(listOperation.index(key, i));
      }
      return dataList;
  }
   
  /**
   * 缓存Set
   *
   * @param key     缓存键值
   * @param dataSet 缓存的数据
   * @return 缓存数据的对象
   */
  public <T> BoundSetOperations<String, T> setCacheSet(String key, Set<T> dataSet) {
      BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
      Iterator<T> it = dataSet.iterator();
      while (it.hasNext()) {
          setOperation.add(it.next());
      }
      return setOperation;
  }
   
  /**
   * 获得缓存的set
   *
   * @param key
   * @return
   */
  public <T> Set<T> getCacheSet(String key) {
      Set<T> dataSet = new HashSet<>();
      BoundSetOperations<String, T> operation = redisTemplate.boundSetOps(key);
      dataSet = operation.members();
      return dataSet;
  }
   
  /**
   * 缓存Map
   *
   * @param key
   * @param dataMap
   * @return
   */
  public <T> HashOperations<String, String, T> setCacheMap(String key, Map<String, T> dataMap) {
      HashOperations hashOperations = redisTemplate.opsForHash();
      if (null != dataMap) {
          for (Map.Entry<String, T> entry : dataMap.entrySet()) {
              hashOperations.put(key, entry.getKey(), entry.getValue());
          }
      }
      return hashOperations;
  }
   
  /**
   * 获得缓存的Map
   *
   * @param key
   * @return
   */
  public <T> Map<String, T> getCacheMap(String key) {
      Map<String, T> map = redisTemplate.opsForHash().entries(key);
      return map;
  }
   
  /**
   * 获得缓存的基本对象列表
   *
   * @param pattern 字符串前缀
   * @return 对象列表
   */
  public Collection<String> keys(String pattern) {
      return redisTemplate.keys(pattern);
  }
   
  /**
   * @param key
   * @return
   */
  public boolean haskey(String key) {
      return redisTemplate.hasKey(key);
  }
   
  public Long getExpire(String key) {
      return redisTemplate.getExpire(key);
  }
   
   
  public <T> ValueOperations<String, T> setBillObject(String key, List<Map<String, Object>> value) {
      ValueOperations<String, T> operation = redisTemplate.opsForValue();
      operation.set(key, (T) value);
      return operation;
  }
   
  /**
   * 缓存list<Map<String, Object>>
   *
   * @param key      缓存的键值
   * @param value    缓存的值
   * @param timeout  时间
   * @param timeUnit 时间颗粒度
   * @return 缓存的对象
   */
  public <T> ValueOperations<String, T> setBillObject(String key, List<Map<String, Object>> value, Integer timeout, TimeUnit timeUnit) {
      ValueOperations<String, T> operation = redisTemplate.opsForValue();
      operation.set(key, (T) value, timeout, timeUnit);
      return operation;
  }
   
  /**
   * 缓存Map
   *
   * @param key
   * @param dataMap
   * @return
   */
  public <T> HashOperations<String, String, T> setCKdBillMap(String key, Map<String, T> dataMap) {
      HashOperations hashOperations = redisTemplate.opsForHash();
      if (null != dataMap) {
          for (Map.Entry<String, T> entry : dataMap.entrySet()) {
              hashOperations.put(key, entry.getKey(), entry.getValue());
          }
      }
      return hashOperations;
  }
}

测试

  @NoRepeatSubmit(name = "test") 
  @GetMapping("test")
  public Result test() {
      return Result.success("测试阶段!");
  }

15秒内重复点击就会给提示

ProceedingJoinPoint获取返回类型、参数名称/值等

org.aspectj.lang.ProceedingJoinPoint

参数值

Object[] args = joinPoint.getArgs();

参数名称

Signature signature = joinPoint.getSignature();
if (signature instanceof MethodSignature) {
    MethodSignature methodSignature = (MethodSignature) signature;
    String[] properties = methodSignature.getParameterNames();
}

返回值类型

Signature signature = joinPoint.getSignature();
if (signature instanceof MethodSignature) {
    MethodSignature methodSignature = (MethodSignature) signature;
    // 被切的方法
    Method method = methodSignature.getMethod();
    // 返回类型
    Class<?> methodReturnType = method.getReturnType();
    // 实例化
    Object o = methodReturnType.newInstance();
}

全限定类名

Signature signature = joinPoint.getSignature();
signature.getDeclaringTypeName()

方法名

Signature signature = joinPoint.getSignature();
signature.getName()

工具类方法

/**
 * 获取方法和参数内容
 *
 * @param className 类名 非必填
 * @param mothodName 方法名 必填
 * @param properties 参数名称列表
 * @param agrs 参数列表
 * @return
 */
public static String getMothodPropertion(String className, String mothodName, String[] properties, Object ... agrs) {
    StringBuilder sb = new StringBuilder();
    if(null != className) {
        sb.append("类名:[").append(className).append("],");
    }
    sb.append("方法:[").append(mothodName).append("]");
    if (null == agrs) {
        return sb.toString();
    }
    boolean flag = null != properties;
    int propertiesLength = properties.length;
    Object obj;
    for(int i = 0, length = agrs.length; i < length; i++) {
        obj = agrs[i];
        sb.append("\r\n参数索引:[").append(i).append("],");
        if (flag && i < propertiesLength) {
            sb.append("参数名称:[").append(properties[i]).append("],");
        }
        if(null == obj) {
            sb.append("为null");
            continue;
        }
        sb.append("类型:[").append(obj.getClass().getName()).append("],");
        if(obj instanceof Collection) {
            Collection collection = (Collection)obj;
            sb.append("长度:[").append(collection.size()).append("],内容:[").append(collection);
        } else if(obj instanceof Map) {
            Map map = (Map)obj;
            sb.append("长度:[").append(map.size()).append("],内容:[").append(map);
        } else if(obj instanceof Object[]) {
            Object[] objects = (Object[])obj;
            sb.append("长度:[").append(objects.length).append("],内容:[").append(Arrays.asList(objects));
        } else if(obj instanceof String) {
            sb.append("内容:[").append(obj);
        } else {
            sb.append("内容:[").append(String.valueOf(obj));
        }
        sb.append("]");
    }
    return sb.toString();
}

/**
 * 获取参数名和参数值
 *
 * @param proceedingJoinPoint 
 * @return
 */
public String getParam(ProceedingJoinPoint proceedingJoinPoint) {
    Map<String, Object> map = new HashMap<String, Object>();
    Object[] values = proceedingJoinPoint.getArgs();
    String[] names = ((CodeSignature) proceedingJoinPoint.getSignature()).getParameterNames();
    for (int i = 0; i < names.length; i++) {
        map.put(names[i], values[i]);
    }
    return JSONObject.toJSONString(map);
}

效果:

java 类型判断方法

java 类型判断方法有三种,分别是instanceof, isInstance,isAssignableFrom

  • instanceof关键字
String str = "abcd";
System.out.println(str instanceof String); //true;
  • boolean isInstance(Object obj)方法
String str = "abcd";
//自身类.class.isInstance(自身实例或子类实例)  返回true 
System.out.println(String.class.isInstance(str)); //true;
  • boolean isAssignableFrom(Class<> cls)方法
String str = "abcd";
//自身类.class.isAssignableFrom(自身类或子类.class)  返回true 
System.out.println(String.class.isAssignableFrom(str.class)); //true;

注意:isInstance和isAssignableFrom不同的是参数类型不同。

AOP 切入点表达式

Spring AOP支持的AspectJ切入点表达式如下:

execution:用于匹配方法执行的连接点;
within:用于匹配指定类型内的方法执行;
this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;
@within:用于匹配所以持有指定注解类型内的方法;
@target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
@args:用于匹配当前执行的方法传入的参数持有指定注解的执行;
@annotation:用于匹配当前执行方法持有指定注解的方法;
bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法;
reference pointcut:表示引用其他命名切入点,只有@ApectJ风格支持,Schema风格不支持。

AspectJ切入点支持的切入点指示符还有: call、get、set、preinitialization、staticinitialization、initialization、handler、adviceexecution、withincode、cflow、cflowbelow、if、@this、@withincode;但Spring AOP目前不支持这些指示符,使用这些指示符将抛出IllegalArgumentException异常。这些指示符Spring AOP可能会在以后进行扩展。

常用的切入点表达式

  • execution

execution表达式可以用于明确指定方法返回类型,类名,方法名和参数名等与方法相关的部件,并且在Spring中,大部分需要使用AOP的业务场景也只需要达到方法级别即可,因而execution表达式的使用是最为广泛的。如下是execution表达式的语法:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

这里问号表示当前项可以有也可以没有,其中各项的语义如下:

modifiers-pattern:方法的可见性,public、protected等;
ret-type-pattern:方法返回的类型,void、String等;
declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
name-pattern:方法名类型,如buisinessService();
param-pattern:参数类型,如java.lang.String等;
throws-pattern:抛出的异常类型,如java.lang.Exception。

如下是一个使用execution表达式的例子:

execution(public * com.demo.Aspect.addLog(java.lang.String,..))

上述切点表达式将会匹配使用public修饰,返回值为任意类型,并且是com.demo.Aspect类中名称为addLog的方法,方法可以有多个参数,但是第一个参数必须是java.lang.String类型的方法。上述示例中我们使用了…通配符。

关于通配符的类型,主要有两种:

  1. *通配符

该通配符主要用于匹配单个单词,或者是以某个词为前缀或后缀的单词。

如下示例表示返回值为任意类型,在com.spring.service.BusinessObject类中,并且参数个数为零的方法:

execution(* com.spring.service.BusinessObject.*())

下述示例表示返回值为任意类型,在com.spring.service包中,以Business为前缀的类,并且是类中参数个数为零方法:

execution(* com.spring.service.Business*.*())

  1. .. (两个点)通配符

该通配符表示0个或多个项,主要用于declaring-type-pattern和param-pattern中,如果用于declaring-type-pattern中,则表示匹配当前包及其子包,如果用于param-pattern中,则表示匹配0个或多个参数。

如下示例表示匹配返回值为任意类型,并且是com.spring.service包及其子包下的任意类的名称为businessService的方法,而且该方法不能有任何参数:

execution(* com.spring.service..*.businessService())

这里需要说明的是,包路径service…*.businessService()中的…应该理解为延续前面的service路径,表示到service路径为止,或者继续延续service路径,从而包括其子包路径;后面的 *.businessService(),这里的 *表示匹配一个单词,因为是在方法名前,因而表示匹配任意的类。

如下示例是使用…表示任意个数的参数的示例,需要注意,表示参数的时候可以在括号中事先指定某些类型的参数,而其余的参数则由…进行匹配:

execution(* com.spring.service.BusinessObject.businessService(java.lang.String,..))

  • within

within表达式的粒度为类,其参数为全路径的类名(可使用通配符),表示匹配当前表达式的所有类都将被当前方法环绕。

如下是within表达式的语法:

within(declaring-type-pattern)

within表达式路径和类名都可以使用通配符进行匹配,比如如下表达式将匹配com.spring.service包下的所有类,不包括子包中的类:

within(com.spring.service.*)

如下表达式表示匹配com.spring.service包及子包下的所有类:

within(com.spring.servic..*)

  • args

args表达式的作用是匹配指定参数类型和指定参数数量的方法,无论其类路径或者是方法名是什么。这里需要注意的是,args指定的参数必须是全路径的。

如下是args表达式的语法:

args(param-pattern)

如下示例表示匹配所有只有一个参数,并且参数类型是java.lang.String类型的方法:

args(java.lang.String)

也可以使用通配符,但这里通配符只能使用…,而不能使用*。如下是使用通配符的实例,该切点表达式将匹配第一个参数为java.lang.String,最后一个参数为java.lang.Integer,并且中间可以有任意个数和类型参数的方法:

args(java.lang.String,..,java.lang.Integer)

  • @within、@annotation、@args

@within@annotation分别表示匹配使用指定注解标注的类和标注的方法将会被匹配,@args则表示使用指定注解标注的类作为某个方法的参数时该方法将会被匹配。

@within和@annotation的区别:

@within 对象级别
@annotation 方法级别

@Around("@within(org.springframework.web.bind.annotation.RestController") 
        这个用于拦截标注在类上面的@RestController注解

@Around("@annotation(org.springframework.web.bind.annotation.RestController") 
         这个用于拦截标注在方法上面的@RestController注解

参考链接:
https://www.jb51.net/article/244302.htm
https://blog.csdn.net/weixin_44259233/article/details/118691977
https://blog.csdn.net/qq_36400075/article/details/111479131
https://blog.csdn.net/qq_23167527/article/details/78623639

posted @ 2023-02-10 19:14  快乐小洋人  阅读(1678)  评论(0编辑  收藏  举报