自定义注解结合AOP实现防重复提交拦截

当在网速不好,或者用户有意快速点击时,会出现这种情况:
自定义注解结合AOP实现防重复提交拦截_第1张图片
这样导致一条数据在同一时间添加多条!需要防止这种行为!

防重复提交的方法有很多种,例如:

  1. 通过JavaScript屏蔽提交按钮
  2. 给数据库增加唯一键约束
  3. 利用Session防止表单重复提交
  4. 使用AOP自定义切入实现
    …等等

本文通过自定义注解与Spring aop实现防重复提交流程做一个说明。
点击查看:自定义注解浅析

1.首先自定义注解:

import java.lang.annotation.*;

/**
 * 避免重复提交
 * @author 
 * @version
 * @since
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AvoidRepeatableCommit {

    /**
     * 指定时间内不可重复提交,单位毫秒,默认10000毫秒
     */
    long timeout()  default 10000 ;
}

2.创建切面类:

/**
 * 重复提交aop
 */
@Aspect//
@Component
public class AvoidRepeatableCommitAspect {
	private static final Logger logger = Logger.getLogger(AvoidRepeatableCommitAspect.class);
    @SuppressWarnings("rawtypes")
	@Autowired
    private RedisTemplate redisTemplate;

    /** 
     * @param point 连接点
     */
    @SuppressWarnings("unchecked")
	@Around("@annotation(com.***.annotation.AvoidRepeatableCommit)")//切面拦截
    public Object around(ProceedingJoinPoint point) throws Throwable {
    	ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    	HttpServletRequest request = attributes.getRequest();
        String ip = IPUtil.getIP(request);
        //此处method获取的是代理对象(由代理模式生成的)的方法
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        //此处realMethod是目标对象(原始的)的方法
		// Method realMethod = point.getTarget().getClass().getDeclaredMethod(signature.getName(),method.getParameterTypes()); 
        		
        //目标类、方法
        String className = method.getDeclaringClass().getName();
        String name = method.getName();
        String ipKey = String.format("%s#%s",className,name);
        int hashCode = Math.abs(ipKey.hashCode());
        String key = String.format("%s_%d",ip,hashCode);
		//logger.info("ipKey={},hashCode={},key={}",ipKey,hashCode,key);
        logger.info(String.format("ipKey={},hashCode={},key={}",ipKey,hashCode,key));
        //通过反射技术来获取注解对象
        AvoidRepeatableCommit avoidRepeatableCommit =  method.getAnnotation(AvoidRepeatableCommit.class);
        long timeout = avoidRepeatableCommit.timeout();
        if (timeout < 0){
            //过期时间10秒
            timeout = 10000;
        }
        //获取key键对应的值
        String value = (String) redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(value)){
            return new Message(1,"请勿重复提交!");
        }
        //新增一个字符串类型的值,key是键,value是值。
        redisTemplate.opsForValue().set(key, UUIDUtil.uuid(),timeout,TimeUnit.MILLISECONDS);
        
        //返回继续执行被拦截到的方法    
        return point.proceed();
    }

}

让我们来仔细解读一下这个类

类有两个注释,分别是@Component@Aspect
@Component是使得AvoidRepeatableCommitAspect 受Spring托管并实例化。
@Aspect就是使得这个类具有AOP功能(你可以这样理解)两个注解缺一不可

类里面只有一个方法,名字叫做around,其实就是为了防重复提交的!

3.在我需要防重复提交的方法上添加 自定义注解:

	// 新增
	@AvoidRepeatableCommit //自定义注解
	@RequestMapping(method = RequestMethod.POST)
	public @ResponseBody Message create(SourceEntity sourceEntity) {
		//设置创建时间
		sourceEntity.setGmt_create(new Date());
		//保存数据库
		sourceEntity.save(sourceEntity);
		return MessageUtil.message("sourceEntity.create.success");
	}

试验效果:
自定义注解结合AOP实现防重复提交拦截_第2张图片
可以看到,无论手速多快,一次只会提交一条数据了;

注意:
这里引入了ProceedingJoinPoint,在使用了@Around之后可以带入这个参数,代表的其实就是我的create这个函数,不过做了一些封装。
point.proceed();就是执行这个方法。

//返回继续执行被拦截到的方法    
  return point.proceed();

那么她是怎么找到在保存方法前拦截的呢?

@Around("@annotation(com.***.annotation.AvoidRepeatableCommit)")//切面拦截

@Around表示包围一个函数,也就是可以在函数执行前做一些事情,也可以在函数执行后做一些事情
具体各种通知可以参考大佬文章,这里就不叙述了。
点击查看:Spring中的AOP以及切入点表达式和各种通知

好了到这里就完成了,主要步骤就是通过每次提交表单时,Aspect都会保存当前key到reids(先设置过期时间)。重复提交时Aspect会判断当前redis是否有该key,若有则拦截。没有就放行。

你可能感兴趣的:(java)