Spring注解方式防止重复提交原理详解

网友投稿 454 2023-01-18


Spring注解方式防止重复提交原理详解

Srping注解方式防止重复提交原理分析,供大家参考,具体内容如下

方法一: Springmvc使用Token

使用token的逻辑是,给所有的url加一个拦截器,在拦截器里面用java的UUID生成一个随机的UUID并把这个UUID放到session里面,然后在浏览器做数据提交的时候将此UUID提交到服务器。服务器在接收到此UUID后,检查一下该UUID是否已经被提交,如果已经被提交,则不让逻辑继续执行下去…**

1 首先要定义一个annotation: 用@Retention 和 @Target 标注接口

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Token {

boolean save() default false;

boolean remove() default false;

}

2 定义拦截器TokenInterceptor:

public class TokenInterceptor extends HandlerInterceptorAdapter {

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

if (handler instanceof HandlerMethod) {

HandlerMethod handlerMethod = (HandlerMethod) handler;

Method method = handlerMethod.getMethod();

Token annotation = method.getAnnotation(Token.class);

if (annotation != null) {

boolean needSaveSessimhQGFQYohon = annotation.save();

if (needSaveSession) {

request.getSession(false).setAttribute("token", UUID.randomUUID().toString());

}

boolean needRemoveSession = annotation.remove();

if (needRemoveSession) {

if (isRepeatSubmit(request)) {

return false;

}

request.getSession(false).removeAttribute("token");

}

}

return true;

} else {

return super.preHandle(request, response, handler);

}

}

private boolean isRepeatSubmit(HttpServletRequest request) {

String serverToken = (String) request.getSession(false).getAttribute("token");

if (serverToken == null) {

return true;

}

String clinetToken = request.getParameter("token");

if (clinetToken == null) {

return true;

}

if (!serverToken.equals(clinetToken)) {

return true;

}

return false;

}

}

Spring MVC的配置文件里加入:

@RequestMapping("/add.jspf")

@Token(save=true)

public String add() {

//省略

return TPL_BASE + "index";

}

@RequestMapping("/save.jspf")

@Token(remove=true)

public void save() {

//省略

}

用法:

在Controller类的用于定向到添加/修改操作的方法上增加自定义的注解类 @Token(save=true)

在Controller类的用于表单提交保存的的方法上增加@Token(remove=true)

在表单中增加 用于存储token,每次需要报token值传入到后台类,用于从缓存对比是否是重复提交操作

方法二:springboot中用注解方式

每次操作,生成的key存放于缓存中,比如用google的Gruava或者Redis做缓存

定义Annotation类

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

public @interface LocalLock {

/**

* @author fly

*/

String key() default "";

/**

* 过期时间 TODO 由于用的 guava 暂时就忽略这属性吧 集成 redis 需要用到

*

* @author fly

*/

int expire() default 5;

}

设置拦截类

@Aspect

@Configuration

public class LockMethodInterceptor {

private static final Cache CACHES = CacheBuilder.newBuilder()

// 最大缓存 100 个

.maximumSize(1000)

// 设置写缓存后 5 秒钟过期

.expireAfterWrite(5, TimeUnit.SECONDS)

.build();

@Around("execution(public * *(..)) && @annotation(com.demo.testduplicate.Test1.LocalLock)")

public Object interceptor(ProceedingJoinPoint pjp) {

MethodSignature signature = (MethodSignature) pjp.getSignature();

Method method = signature.getMethod();

LocalLock localLock = method.getAnnotation(LocalLock.class);

String key = getKey(localLock.key(), pjp.getArgs());

if (!StringUtils.isEmpty(key)) {

if (CACHES.getIfPresent(key) != null) {

throw new RuntimeException("请勿重复请求");

}

// 如果是第一次请求,就将 key 当前对象压入缓存中

CACHES.put(key, key);

}

try {

return pjp.proceed();

} catch (Throwable throwable) {

throw new RuntimeException("服务器异常");

} finally {

// TODO 为了演示效果,这里就不调用 CACHES.invalidate(key); 代码了

}

}

/**

* key 的生成策略,如果想灵活可以写成接口与实现类的方式(TODO 后续讲解)

*

* @param keyExpress 表达式

* @param args 参数

* @return 生成的key

*/

private String getKey(String keyExpress, Object[] args) {

for (int i = 0; i < args.length; i++) {

keyExpress = keyExpress.replace("arg[" + i + "]", args[i].toString());

}

return keyExpress;

}

}

Controller类引用

@RestController

@RequestMapping("/books")

public class BookController {

@LocalLock(key = "book:arg[0]")

@GetMapping

public String save(@RequestParam String token) {

return "success - " + token;

}

}


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Centos下SpringBoot项目启动与停止脚本的方法
下一篇:详解如何使用IntelliJ IDEA新建一个Servlet项目
相关文章

 发表评论

暂时没有评论,来抢沙发吧~