SpringBoot
To simplify theSpring
creation of applications, run, debug, deploy a range of issues such as the birth of the product, characteristics of the automatic assembly so that we can better focus on the business itself, rather than external XML configuration, we simply follow the norms related to the introduction of dependence can easily build a WEB project
In normal development, if at a relatively slow speed situations, the user submits the form, the discovery server for a long time are not responding, the user may think that he did not submit the form and click the submit button Repeat submit the form, we are developing forms must prevent duplicate submission ....
repeated submit
Literally means to submit a lot of times, this is usually the front to dig your pit ....
Earlier encountered in the development of such a problem; there is a call interface when a front end little brother cycle call problem, normally a request to add a data transmission, the result becomes the same timing of the concurrent transmission of the N requests, service end moment ignorant forced to insert the N pieces of exactly the same data, the front end of the small brother did not know where the problem lies ( 恩...坑就这样挖好了,反正不填坑,气死你
) this time we supposed to do; the back-end dry chant, anyway, dirty work, scapegoat thing no less dry , not much more than a ....
Chapter Objectives
Use 自定义注解
, Spring Aop
, Guava Cache
to achieve the anti-form repeatedly submit 不适用于分布式哦,后面会讲分布式方式...
( )
Specific code
very simple…
Import dependence
In pom.xml
the add spring-boot-starter-web
-dependent can
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>21.0</version>
</dependency>
</dependencies>
Lock Notes
Create an LocalLock
annotation, a simple point on key
it, due to a temporary unused redis
it expire
is furnished ....
package com.battcn.annotation;
import java.lang.annotation.*;
/**
* 锁的注解
*
* @author Levin
*/
@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;
}
Lock interceptor (AOP)
First, by CacheBuilder.newBuilder()
building a cache object, set the expiration time; its purpose is to prevent crashes lock is not released (of course, if a stand-alone program in this way are blown up, lock early gone; but this does not prevent us write point )
In particular interceptor()
use it is on Around(环绕增强)
all bands LocalLock
annotated section will be processed;
If you want more flexible, key generation rules can be defined as the interface form ( refer to: org.springframework.cache.interceptor.KeyGenerator ), here the lazy;
package com.battcn.interceptor;
import com.battcn.annotation.LocalLock;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
/**
* 本章先基于 本地缓存来做,后续讲解 redis 方案
*
* @author Levin
* @since 2018/6/12 0012
*/
@Aspect
@Configuration
public class LockMethodInterceptor {
private static final Cache<String, Object> CACHES = CacheBuilder.newBuilder()
// 最大缓存 100 个
.maximumSize(1000)
// 设置写缓存后 5 秒钟过期
.expireAfterWrite(5, TimeUnit.SECONDS)
.build();
@Around("execution(public * *(..)) && @annotation(com.battcn.annotation.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;
}
}
Control Layer
Added interface @LocalLock(key = "book:arg[0]")
; means that will arg[0]
replace the value of the first parameter, the new key generation to be cached;
package com.battcn.controller;
import com.battcn.annotation.LocalLock;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* BookController
*
* @author Levin
* @since 2018/6/06 0031
*/
@RestController
@RequestMapping("/books")
public class BookController {
@LocalLock(key = "book:arg[0]")
@GetMapping
public String query(@RequestParam String token) {
return "success - " + token;
}
}
The main function
package com.battcn;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author Levin
*/
@SpringBootApplication
public class Chapter21Application {
public static void main(String[] args) {
SpringApplication.run(Chapter21Application.class, args);
}
}
test
Upon completion of preparation matters, start Chapter21Application
self-testing can, I believe everyone testing means are not familiar, such as 浏览器
, , postman
, junit
, swagger
based here postman
, if you feel that comes with exception information is not friendly enough, then coupled with clever use of SpringBoot easy to get global exception be easy to get ...
The first request
The second request