SpringBoot-- prevent duplicate submission (locking mechanism locks --- local, distributed lock)

  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:

 

Guess you like

Origin www.cnblogs.com/liconglong/p/11728136.html