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();
}
}