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();@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate RedisLockClient redisLockClient;@Autowiredprivate 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);}@Overridepublic 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 {@Autowiredprivate 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();
}
}