美文网首页
SpringBoot 2.x 实现Redis分布式锁

SpringBoot 2.x 实现Redis分布式锁

作者: 懂码哥 | 来源:发表于2020-03-08 22:01 被阅读0次

    annotation注解

    CacheLock
    • 属性说明:
      • prefix: 缓存中 key 的前缀
      • expire: 过期时间,此处默认为 5 秒
      • timeUnit: 超时单位,此处默认为秒
      • delimiter: key 的分隔符,将不同参数值分割开
    • 文档注解说明:
      • @Target(ElementType.METHOD) : Annotation所修饰的对象范围

        取值(ElementType)有:
        1.CONSTRUCTOR:用于描述构造器
            2.FIELD:用于描述域
            3.LOCAL_VARIABLE:用于描述局部变量
            4.METHOD:用于描述方法
            5.PACKAGE:用于描述包
            6.PARAMETER:用于描述参数
            7.TYPE:用于描述类、接口(包括注解类型) 或enum声明

      • @Retention(RetentionPolicy.RUNTIME) : 注解生命周期

        1、RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
        2、RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
        3、RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
        这3个生命周期分别对应于:Java源文件(.java文件) ---> .class文件 ---> 内存中的字节码。
        生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。

      • @Documented : javadoc工具记录

      • @Inherited : 被元注解Inherited修饰的注解,只有作用在类上时,会被子类继承此自定义的注解,其余情况都不会继承

    package com.gerrywen.seckill.common.redislock.annotation;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Inherited;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import java.util.concurrent.TimeUnit;
    
    /**
     * 锁的注解
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    public @interface CacheLock {
    
        /**
         * redis 锁key的前缀
         *
         * @return redis 锁key的前缀
         */
        String prefix() default "";
    
        /**
         * 过期秒数,默认为5秒
         *
         * @return 轮询锁的时间
         */
        int expire() default 5;
    
        /**
         * 超时时间单位
         *
         * @return 秒
         */
        TimeUnit timeUnit() default TimeUnit.SECONDS;
    
        /**
         * Key的分隔符(默认 :)
         * 生成的Key:N:SO01:001
         *
         * @return String
         */
        String delimiter() default ":";
    }
    
    CacheLockParam
    package com.gerrywen.seckill.common.redislock.annotation;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Inherited;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * 锁的参数
     */
    @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    public @interface CacheLockParam {
        /**
         * 字段名称
         *
         * @return String
         */
        String name() default "";
    }
    

    component生成策略

    CacheKeyGenerator
    package com.gerrywen.seckill.common.redislock.component;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    
    /**
     * program: spring-boot-seckill->CacheKeyGenerator
     * description: key生成器
     * author: gerry
     * created: 2020-03-08 20:32
     **/
    public interface CacheKeyGenerator {
    
    
        /**
         * 获取AOP参数,生成指定缓存Key
         * @param pjp
         * @return 缓存KEY
         */
        String getLockKey(ProceedingJoinPoint pjp);
    }
    
    LockKeyGenerator
    package com.gerrywen.seckill.common.redislock.component;
    
    import com.gerrywen.seckill.common.redislock.annotation.CacheLock;
    import com.gerrywen.seckill.common.redislock.annotation.CacheLockParam;
    import org.apache.commons.lang3.StringUtils;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.util.ReflectionUtils;
    
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.lang.reflect.Parameter;
    
    /**
     * program: spring-boot-seckill->LockKeyGenerator
     * description: 通过接口注入的方式去写不同的生成规则;
     * author: gerry
     * created: 2020-03-08 20:44
     **/
    public class LockKeyGenerator implements CacheKeyGenerator {
        @Override
        public String getLockKey(ProceedingJoinPoint pjp) {
            MethodSignature signature = (MethodSignature) pjp.getSignature();
            Method method = signature.getMethod();
            CacheLock lockAnnotation = method.getAnnotation(CacheLock.class);
            final Object[] args = pjp.getArgs();
            final Parameter[] parameters = method.getParameters();
            StringBuilder builder = new StringBuilder();
            //默认解析方法里面带 CacheLockParam 注解的属性,如果没有尝试着解析实体对象中的
            for (int i = 0; i < parameters.length; i++) {
                final CacheLockParam annotation = parameters[i].getAnnotation(CacheLockParam.class);
                if (annotation == null) {
                    continue;
                }
                builder.append(lockAnnotation.delimiter()).append(args[i]);
            }
            if (StringUtils.isEmpty(builder.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) {
                        final CacheLockParam annotation = field.getAnnotation(CacheLockParam.class);
                        if (annotation == null) {
                            continue;
                        }
                        field.setAccessible(true);
                        builder.append(lockAnnotation.delimiter()).append(ReflectionUtils.getField(field, object));
                    }
                }
            }
            return lockAnnotation.prefix() + builder.toString();
        }
    }
    

    Lock拦截器(AOP)

    LockMethodInterceptor
    package com.gerrywen.seckill.common.redislock.interceptor;
    
    import com.gerrywen.seckill.common.redislock.annotation.CacheLock;
    import com.gerrywen.seckill.common.redislock.component.CacheKeyGenerator;
    import com.gerrywen.seckill.common.redislock.component.LockKeyGenerator;
    import org.apache.commons.lang3.StringUtils;
    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.core.StringRedisTemplate;
    
    import java.lang.reflect.Method;
    
    /**
     * program: spring-boot-seckill->LockMethodInterceptor
     * description: Redis方案实现分布式锁
     * author: gerry
     * created: 2020-03-08 20:50
     **/
    @Aspect
    @Configuration
    public class LockMethodInterceptor {
    
        private final StringRedisTemplate lockRedisTemplate;
        private final CacheKeyGenerator cacheKeyGenerator;
    
        @Autowired
        public LockMethodInterceptor(StringRedisTemplate lockRedisTemplate) {
            this.lockRedisTemplate = lockRedisTemplate;
            this.cacheKeyGenerator =  new LockKeyGenerator();
        }
    
        @Around("execution(public * *(..)) && @annotation(com.gerrywen.seckill.common.redislock.annotation.CacheLock)")
        public Object interceptor(ProceedingJoinPoint pjp) {
            MethodSignature signature = (MethodSignature) pjp.getSignature();
            Method method = signature.getMethod();
            CacheLock lock = method.getAnnotation(CacheLock.class);
            if (StringUtils.isEmpty(lock.prefix())) {
                throw new RuntimeException("lock key can't be null...");
            }
            final String lockKey = cacheKeyGenerator.getLockKey(pjp);
            try {
                //key不存在才能设置成功
                final Boolean success = lockRedisTemplate.opsForValue().setIfAbsent(lockKey, "");
                if (success != null && success) {
                    lockRedisTemplate.expire(lockKey, lock.expire(), lock.timeUnit());
                } else {
                    //按理来说 我们应该抛出一个自定义的 CacheLockException 异常;
                    throw new RuntimeException("请勿重复请求");
                }
                try {
                    return pjp.proceed();
                } catch (Throwable throwable) {
                    throw new RuntimeException("系统异常");
                }
    
            } finally {
    //             lockRedisTemplate.delete(lockKey);
            }
    
        }
    }
    

    控制器方法使用

        @CacheLock(prefix = "cacheLock")
        @RequestMapping("/cacheLock")
        @ResponseBody
        public String query(@CacheLockParam(name = "token") @RequestParam String token) {
            return "success - " + token;
        }
    

    项目目录结构

    image.png

    相关文章

      网友评论

          本文标题:SpringBoot 2.x 实现Redis分布式锁

          本文链接:https://www.haomeiwen.com/subject/ihxsdhtx.html