SpringBoot分布式锁自定义注解处理幂等性

发布于:2024-03-30 ⋅ 阅读:(49) ⋅ 点赞:(0)

SpringBoot分布式锁+自定义注解处理幂等性

注解简介

注解(Annotation)是Java SE 5.0 版本开始引入的概念,它是对 Java 源代码的说明,是一种元数据(描述数据的数据)。

Java中的注解主要分为以下三类:

JDK的注解
第三方的注解
自定义注解

JDK注解

Java内置注解
    @Override (标记重写方法)
    @Deprecated (标记过时)
    @SuppressWarnings (忽略警告)
元注解 (注解的注解)
    @Target (注解的作用目标)
    @Retention (注解的生命周期)
    @Document (注解是否被包含在JavaDoc中)
    @Inherited (是否允许子类集成该注解)
@Target

用于描述注解的使用范围,有一个枚举ElementType来指定,具体如下:

Target类型 描述
ElementType.TYPE 应用于类、接口(包括注解类型)、枚举
ElementType.FIELD 应用于属性(包括枚举中的常量)
ElementType.METHOD 应用于方法
ElementType.PARAMETER 应用于方法的形参
ElementType.CONSTRUCTOR 应用于构造函数
ElementType.LOCAL_VARIABLE 应用于局部变量
ElementType.ANNOTATION_TYPE 应用于注解类型
ElementType.PACKAGE 应用于包
ElementType.TYPE_PARAMETER 应用于类型变量
ElementType.TYPE_USE 应用于任何使用类型的语句中(例如声明语句、泛型和强制转换语句中的类型)
@Retention

表示需要在什么级别保存该注释信息,用于描述注解的生命周期,也是一个枚举RetentionPoicy来决定的

取值 含义
RetentionPolicy.SOURCE 源码中保留,编译期可以处理
RetentionPolicy.CLASS Class文件中保留,Class加载时可以处理
RetentionPolicy.RUNTIME 运行时保留,运行中可以处理

一般填RetentionPoicy.RUNTIME即可

@Documented

如果用javadoc生成文档时,想把注解也生成文档,就带这个。

@Inherited

@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。注意,@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。

自定义注解

使用JDK中一些元注解,@Target,@Retention,@Document,@Inherited来修饰注解。具体格式如下:
在这里插入图片描述

自定义注解实例:

import org.springblade.core.redis.lock.LockType;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁注解处理幂等性
 * 分布式锁注解,Redisson,支持的锁的种类有很多,适合注解形式的只有重入锁、公平锁
 *
 * <p>
 * 1. 可重入锁(Reentrant Lock)
 * 2. 公平锁(Fair Lock)
 * 3. 联锁(MultiLock)
 * 4. 红锁(RedLock)
 * 5. 读写锁(ReadWriteLock)
 * </p>
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface RedisIdempotentLock {

    /**
     * 分布式锁的 key,必须:请保持唯一性
     *
     * @return key
     */
    String prefix() default "";

    /**
     * 分布式锁参数,可选,支持 spring el # 读取方法参数和 @ 读取 spring bean
     *
     * @return param
     */
    String param() default "";

    /**
     * 使用用户id作为新增等接口作为唯一key,处理幂等
     *
     * @return param
     */
    boolean isUserId() default false;
    /**
     * 等待锁超时时间,默认30
     *
     * @return int
     */
    long waitTime() default 30;

    /**
     * 自动解锁时间,自动解锁时间一定得大于方法执行时间,否则会导致锁提前释放,默认-1
     *
     * @return int
     */
    long leaseTime() default -1;

    /**
     * 时间单温,默认为秒
     *
     * @return 时间单位
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;

    /**
     * 默认公平锁
     *
     * @return LockType
     */
    LockType type() default LockType.FAIR;

}


AOP 切面通用分布式锁+自定义注解处理幂等性;

import com.zhkj.ims.anotation.RedisIdempotentLock;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.redis.lock.LockType;
import org.springblade.core.redis.lock.RedisLockClient;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.spel.BladeExpressionEvaluator;
import org.springblade.core.tool.utils.CharPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * redis 分布式锁处理幂等性
 */
@Aspect
@Component
public class RedisIdempotentLockAspect implements ApplicationContextAware {

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisIdempotentLockAspect.class);

    /**
     * 表达式处理
     */
    private static final BladeExpressionEvaluator EVALUATOR = new BladeExpressionEvaluator();
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedisLockClient redisLockClient;
    @Autowired
    private ApplicationContext applicationContext;

    private static final String DEFAULT_SUPER_PREFIX = "idempotence";

    /**
     * AOP 环切 注解 @RedisIdempotentLock
     */
    @Around(value = "@annotation(redisIdempotentLock)")
    public Object aroundRedisLock(ProceedingJoinPoint point, RedisIdempotentLock redisIdempotentLock) throws Throwable {
        String prefix = redisIdempotentLock.prefix();
        Class clazz = point.getTarget().getClass();
        String methodName = point.getSignature().getName();
        String lockName;
        String fullClassName = clazz.getName();
        if (StringUtils.hasText(fullClassName)) {
            String[] splits = fullClassName.split("\\.");
            String className = splits[splits.length - 1];
            lockName = className + CharPool.COLON + methodName;
        } else {
            lockName = methodName;
        }
        lockName = StringUtils.hasText(prefix) ? DEFAULT_SUPER_PREFIX + CharPool.COLON + prefix + CharPool.COLON + lockName : DEFAULT_SUPER_PREFIX + CharPool.COLON + lockName;
        String lockParam = redisIdempotentLock.param();
        String lockKey;
        if (StringUtil.isNotBlank(lockParam)) {
            // 解析表达式
            String evalAsText = evalLockParam(point, lockParam);
            lockKey = lockName + CharPool.COLON + evalAsText;
            if (redisIdempotentLock.isUserId()) {
                lockKey = lockKey + CharPool.COLON + AuthUtil.getUserId();
            }
        } else {
            if (redisIdempotentLock.isUserId()) {
                lockKey = lockName + CharPool.COLON + AuthUtil.getUserId();
            } else {
                lockKey = lockName;
            }
        }
        LockType lockType = redisIdempotentLock.type();
        long waitTime = redisIdempotentLock.waitTime();
        long leaseTime = redisIdempotentLock.leaseTime();
        TimeUnit timeUnit = redisIdempotentLock.timeUnit();
        Object result;
        boolean release = false;
        if (existKey(lockKey)) {
            throw new ServiceException("操作进行中,请稍后重试!");
        }
        try {
            boolean tryLock = redisLockClient.tryLock(lockKey, lockType, waitTime, leaseTime, timeUnit);
            if (tryLock) {
                release = true;
                result = point.proceed();
            } else {
                throw new ServiceException("操作进行中,请稍后重试!");
            }
        } catch (Exception e) {
            LOGGER.info("方法处理异常:{}", e.getMessage());
            throw e;
        } finally {
            if (release && existKey(lockKey)) {
                LOGGER.info("释放锁key:{}", lockKey);
                redisLockClient.unLock(lockKey, lockType);
            }
        }
        return result;
    }

    /**
     * 计算参数表达式
     *
     * @param point     ProceedingJoinPoint
     * @param lockParam lockParam
     * @return 结果
     */
    private String evalLockParam(ProceedingJoinPoint point, String lockParam) {
        MethodSignature ms = (MethodSignature) point.getSignature();
        Method method = ms.getMethod();
        Object[] args = point.getArgs();
        Object target = point.getTarget();
        Class<?> targetClass = target.getClass();
        EvaluationContext context = EVALUATOR.createContext(method, args, target, targetClass, applicationContext);
        AnnotatedElementKey elementKey = new AnnotatedElementKey(method, targetClass);
        return EVALUATOR.evalAsText(lockParam, elementKey, context);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    private boolean existKey(String lockKey) {
        Assert.hasText(lockKey, "lockKey must not null.");
        return redisTemplate.hasKey(lockKey);
    }
}

​ 具体使用

@RestController
public class TestController {

   @Autowired
   private JdbcConfig jdbcConfig;

   @RedisIdempotentLock(param = "#id")
   @PostMapping("/hello")
   public String Hello(Long id) {
       return jdbcConfig.getUrl() + "  " + jdbcConfig.getDriver() + " " + jdbcConfig.getUser() + " " + jdbcConfig.getPassword();
   }
}

{

@Autowired
private JdbcConfig jdbcConfig;

@RedisIdempotentLock(param = “#id”)
@PostMapping(“/hello”)
public String Hello(Long id) {
return jdbcConfig.getUrl() + " " + jdbcConfig.getDriver() + " " + jdbcConfig.getUser() + " " + jdbcConfig.getPassword();
}
}

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到