SpringBoot--防止重复提交(分布式锁实现)

Lewis
2021-03-08 / 0 评论 / 68 阅读 / 正在检测是否收录...
防止重复提交,主要是使用锁的形式来处理,如果是单机部署,可以使用本地缓存锁(Guava)即可,如果是分布式部署,则需要使用分布式锁(可以使用zk分布式锁或者redis分布式锁),本文的分布式锁以redis分布式锁为例。

1.自定义分布式锁注解

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CacheLock {
    //redis锁前缀
    String prefix() default "";
    //redis锁过期时间
    int expire() default 5;
    //redis锁过期时间单位
    TimeUnit timeUnit() default TimeUnit.SECONDS;
    //redis  key分隔符
    String delimiter() default ":";
}

2.自定义key规则注解

由于redis的key可能是多层级结构,例如 redistest:demo1:token:kkk这种形式,因此需要自定义key的规则。
import java.lang.annotation.*;

@Target({ElementType.METHOD,ElementType.PARAMETER,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CacheParam {
    String name() default "";
}

3.定义key生成策略接口

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Service;

public interface CacheKeyGenerator {
    //获取AOP参数,生成指定缓存Key
    String getLockKey(ProceedingJoinPoint joinPoint);
}

4.定义key生成策略实现类

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 class CacheKeyGeneratorImp implements CacheKeyGenerator {
    @Override
    public String getLockKey(ProceedingJoinPoint joinPoint) {
        //获取连接点的方法签名对象
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        //Method对象
        Method method = methodSignature.getMethod();
        //获取Method对象上的注解对象
        CacheLock cacheLock = method.getAnnotation(CacheLock.class);
        //获取方法参数
        final Object[] args = joinPoint.getArgs();
        //获取Method对象上所有的注解
        final Parameter[] parameters = method.getParameters();
        StringBuilder sb = new StringBuilder();
        for(int i=0;i<parameters.length;i++){
            final CacheParam cacheParams = parameters[i].getAnnotation(CacheParam.class);
            //如果属性不是CacheParam注解,则不处理
            if(cacheParams == null){
                continue;
            }
            //如果属性是CacheParam注解,则拼接 连接符(:)+ CacheParam
            sb.append(cacheLock.delimiter()).append(args[i]);
        }
        //如果方法上没有加CacheParam注解
        if(StringUtils.isEmpty(sb.toString())){
            //获取方法上的多个注解(为什么是两层数组:因为第二层数组是只有一个元素的数组)
            final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
            //循环注解
            for(int i=0;i<parameterAnnotations.length;i++){
                final Object object = args[i];
                //获取注解类中所有的属性字段
                final Field[] fields = object.getClass().getDeclaredFields();
                for(Field field : fields){
                    //判断字段上是否有CacheParam注解
                    final CacheParam annotation = field.getAnnotation(CacheParam.class);
                    //如果没有,跳过
                    if(annotation ==null){
                        continue;
                    }
                    //如果有,设置Accessible为true(为true时可以使用反射访问私有变量,否则不能访问私有变量)
                    field.setAccessible(true);
                    //如果属性是CacheParam注解,则拼接 连接符(:)+ CacheParam
                    sb.append(cacheLock.delimiter()).append(ReflectionUtils.getField(field,object));
                }
            }
        }
        //返回指定前缀的key
        return cacheLock.prefix() + sb.toString();
    }
}

5.分布式注解实现

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 new RuntimeException("前缀不能为空");
        }
        //获取自定义key
        final String lockkey = cacheKeyGenerator.getLockKey(joinPoint);
        final Boolean success = stringRedisTemplate.execute(
                (RedisCallback<Boolean>) connection -> connection.set(lockkey.getBytes(), new byte[0], Expiration.from(cacheLock.expire(), cacheLock.timeUnit())
                        , RedisStringCommands.SetOption.SET_IF_ABSENT));
        if (!success) {
            // TODO 按理来说 我们应该抛出一个自定义的 CacheLockException 异常;这里偷下懒
            throw new RuntimeException("请勿重复请求");
        }
        try {
            return joinPoint.proceed();
        } catch (Throwable throwable) {
            throw new RuntimeException("系统异常");
        }
    }
}

6.主函数调整

    @Bean
    public CacheKeyGenerator cacheKeyGenerator(){
        return new CacheKeyGeneratorImp();
    }

7.Controller

@ResponseBody
    @PostMapping(value ="/cacheLock")
    @ApiOperation(value="重复提交验证测试--使用redis锁")
    @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="重复提交验证测试--使用redis锁")
    @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
    @CacheLock(prefix = "redisLock.test",expire = 20)
    public String cacheLock2(@CacheParam(name = "token") String token){
        return "sucess====="+token;
    }
0

评论 (0)

取消