Prevent duplicate submissions, mainly in the form of lock to use to deal with, if it is a stand-alone deployment, you can use the local cache lock (Guava) can, if it is distributed deployment, you need to use the Distributed Lock (zk can use distributed lock or redis distributed lock), distributed lock the distributed lock paper redis example.
First, the local lock (Guava)
1, import-dependent
<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>
2, custom local locks comment
Package com.example.demo.utils; Import Classes in java.lang.annotation *. ; @Target (ElementType.METHOD {}) @Retention (RetentionPolicy.RUNTIME) @Documented @Inherited public @ interface LocalLock { String Key () default "" ; // expiration time, use the local cache can be ignored if the cache using redis do need int The expire () default 5 ; }
3, the local lock achieve comment
package com.example.demo.utils; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; 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.reflect.MethodSignature; import org.springframework.context.annotation.Configuration; import org.springframework.util.StringUtils; import java.lang.reflect.Method; import java.util.concurrent.TimeUnit; @Aspect @Configuration public class LockMethodInterceptor { //定义缓存,设置最大缓存数及过期日期 private static final Cache<String,Object> CACHE = CacheBuilder.newBuilder().maximumSize(1000).expireAfterWrite(20, TimeUnit.SECONDS).build(); @Around("execution(public * *(..)) && @annotation(com.example.demo.utils.LocalLock)") public Object interceptor(ProceedingJoinPoint joinPoint){ MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); LocalLock localLock = method.getAnnotation(LocalLock.class); String key = getKey(localLock.key(),joinPoint.getArgs()); if(!StringUtils.isEmpty(key)){ if(CACHE.getIfPresent(key) != null){ throw new RuntimeException("请勿重复请求!"); } CACHE.put(key,key); } try{ return joinPoint.proceed(); }catch (Throwable throwable){ throw new RuntimeException("服务器异常"); }finally { } } 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; } }
4, the control layer
@ResponseBody @PostMapping (value = "/ LocalLock" ) @ApiOperation (value = "repeatedly submit validation testing - using local cache lock" ) @ApiImplicitParams ({@ApiImplicitParam (paramType = "Query", name = "token", value = "token", dataType = "String" )}) @LocalLock (Key = "LocalLock: Test: Arg [0]" ) public String LocalLock (String token) { return "Sucess ================" + token; }
5, test
The first request:
Unexpired visit again:
Two, Redis distributed lock
1, import-dependent
Aop import dependence and dependence to redis
2, Configuration
Connection configuration information to redis
3, custom annotations distributed lock
Package com.example.demo.utils; Import Classes in java.lang.annotation *. ; Import java.util.concurrent.TimeUnit; @Target (ElementType.METHOD) @Retention (RetentionPolicy.RUNTIME) @Documented @Inherited public @ interface CacheLock { // Redis lock prefix String prefix () default "" ; // Redis lock expiration time int The expire () default . 5 ; // Redis lock expiration time unit TimeUnit timeUnit () default TimeUnit.SECONDS; // Redis Key delimiter String delimiter() default ":"; }
4, custom key rule comment
Since the redis key may be a multi-level structure, e.g. redistest: demo1: token: kkk this form, it is necessary to key custom rules.
package com.example.demo.utils; import java.lang.annotation.*; @Target({ElementType.METHOD,ElementType.PARAMETER,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface CacheParam { String name() default ""; }
5, the definition of key generation strategy Interface
package com.example.demo.service; import org.aspectj.lang.ProceedingJoinPoint; import org.springframework.stereotype.Service; public interface CacheKeyGenerator { //获取AOP参数,生成指定缓存Key String getLockKey(ProceedingJoinPoint joinPoint); }
6, the definition of key generation strategy implementation class
package com.example.demo.service.impl; import com.example.demo.service.CacheKeyGenerator; import com.example.demo.utils.CacheLock; import com.example.demo.utils.CacheParam; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Parameter; public classCacheKeyGeneratorImp the implements CacheKeyGenerator { @Override public String getLockKey (ProceedingJoinPoint Joinpoint) { // Get the connection point of the method signature object MethodSignature MethodSignature = (MethodSignature) joinPoint.getSignature (); // Method, Object Method, Method = methodSignature.getMethod (); // acquiring annotation object method object CacheLock cacheLock = method.getAnnotation (CacheLock. class ); // Get method parameters final Object [] args = joinPoint.getArgs (); // get all annotations method object on finalThe Parameter [] = Parameters method.getParameters (); the StringBuilder SB = new new the StringBuilder (); for ( int I = 0; I <parameters.length; I ++ ) { Final CacheParam cacheParams = Parameters [I] .getAnnotation (CacheParam. Class ) ; // if the property is not CacheParam annotations, not processed IF (cacheParams == null ) { Continue ; } // if the property is CacheParam annotation, the splice connector (:) + CacheParam sb.append (cacheLock.delimiter ()) .append (args [I]); } // if the method is not applied CacheParam annotations IF (StringUtils.isEmpty (sb.toString ())) { // Get more annotations on process (Why two arrays: Since the second set of layers is only one array element) Final the Annotation [] [] = parameterAnnotations method.getParameterAnnotations (); // cycle annotations for ( int I = 0; I <parameterAnnotations.length; I ++ ) { Final Object = Object args [I]; // get all annotation class attribute field Final field, [ ] = fields object.getClass () getDeclaredFields ();. for (field, field: fields) { // if there is the judgment CacheParam comments field finalAnnotation = field.getAnnotation CacheParam (CacheParam. Class ); // if not, skip IF (Annotation == null ) { Continue ; } // if, Accessible set to true (is true reflection may be used to access a private variable, or can not access private variables) field.setAccessible ( to true ); // if the property is CacheParam comment, the splice connector (:) + CacheParam sb.append (cacheLock.delimiter ().) the append (ReflectionUtils.getField (Field,, Object )); } } } // returns the specified prefix Key returncacheLock.prefix () + sb.ToString (); } }
7, distributed achieve comment
package com.example.demo.utils; import com.example.demo.service.CacheKeyGenerator; 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.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisStringCommands; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.types.Expiration; import org.springframework.util.StringUtils; import java.lang.reflect.Method; @Aspect @Configuration public class CacheLockMethodInterceptor { @Autowired public CacheLockMethodInterceptor(StringRedisTemplate stringRedisTemplate, CacheKeyGenerator cacheKeyGenerator){ this.cacheKeyGenerator = cacheKeyGenerator; this.stringRedisTemplate = stringRedisTemplate; } private final StringRedisTemplate stringRedisTemplate; private final CacheKeyGenerator cacheKeyGenerator; @Around("execution(public * * (..)) && @annotation(com.example.demo.utils.CacheLock)") public Object interceptor(ProceedingJoinPoint joinPoint){ MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Method method = methodSignature.getMethod(); CacheLock cacheLock = method.getAnnotation(CacheLock.class); if(StringUtils.isEmpty(cacheLock.prefix())){ throw newA RuntimeException ( "prefix can not be empty" ); } // Get custom Key Final String lockkey = cacheKeyGenerator.getLockKey (Joinpoint); Final Boolean = Success stringRedisTemplate.execute ( (RedisCallback <Boolean>) Connection -> connection.set (lockkey .getBytes (), new new byte [0 ], Expiration.from (cacheLock.expire (), cacheLock.timeUnit ()) , RedisStringCommands.SetOption.SET_IF_ABSENT)); IF ! ( Success) { // TODO Logically speaking, we should CacheLockException throw a custom exception; here to steal the lazy the throw new new a RuntimeException ( "Do not repeat request"); } try { return joinPoint.proceed(); } catch (Throwable throwable) { throw new RuntimeException("系统异常"); } } }
8, the main function adjusting
The main function introduced key generation strategy
@Bean public CacheKeyGenerator cacheKeyGenerator(){ return new CacheKeyGeneratorImp(); }
9、Controller
@ResponseBody @PostMapping (value = "/ cacheLock" ) @ApiOperation (value = "repeatedly submit validation testing - using redis Lock" ) @ApiImplicitParams ({@ApiImplicitParam (paramType = "Query", name = "token", value = "token", dataType = "String" )}) // @CacheLock @CacheLock () public String cacheLock (String token) { return "Sucess ================" + token; } @ResponseBody @PostMapping (value = "/ cacheLock1 " ) @ApiOperation (value =" repeatedly submit validation testing - using redis lock ") @ApiImplicitParams( {@ApiImplicitParam(paramType="query", name = "token", value = "token", dataType = "String")}) //@CacheLock @CacheLock(prefix = "redisLock.test",expire = 20) public String cacheLock1(String token){ return "sucess====="+token; } @ResponseBody @PostMapping(value ="/cacheLock2") @ApiOperation(value="重复提交验证测试--使用redis锁") @ApiImplicitParams( {@ApiImplicitParam(paramType="query", name = "token", value = "token", dataType = "String") @CacheLock (prefix = "redisLock.test" expire = 20@CacheLock//)}) public String cacheLock2(@CacheParam(name = "token") String token){ return "sucess====="+token; }
10, the test
(1) Since the method does not CacheLock annotation cacheLock prefix plus prefix therefore given
(2) does not add CacheParam comment
First call:
Cache information:
You can find the key to the value prifix
Second call:
(3) increased CacheParam comment
First call:
Cache information:
It can be found in the cache contents prefix + @ CacheParam
Second call: