这是我参与「第五届青训营 」伴学笔记创作活动的第 7 天
前面实现了使用redis上锁,但这么写似乎还不是特别方便,每次使用都要try catch,注入,重复写代码,那有没有更方便的方式呢?为了解决大项目上锁的需求,我去学习了如何利用注解上锁。
思路分析
利用aop切面编程来实现上锁和解锁的事件,以加了注解的方法为切入点,在方法请求前自动上锁,待方法完成后解锁。锁是基于上一节的redis锁的思路,利用setNx函数的性质实现锁。
代码实现
定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Lock {
/**
* key
* @return
*/
String key();
/**
* 锁过期时间
* @return
*/
long ttl() default 10L;
/**
* 是否重新请求
* @return
*/
boolean restart() default false;
/**
* 重新请求的次数
* @return
*/
int rCount() default 3;
/**
* 每次重新请求的等待间隔
* @return
*/
long waitTime() default 200L;
}
这个是上锁的注解,作用在方法上。属性上可以设置锁的key,同时支持SpEL表达式,大大增加了锁的key的灵活性,同时还能设置重新请求等。
注解的实现
@Aspect
@Component
@Slf4j
public class LockAnnotationParser {
@Resource
private RedisService redisService;
/**
* 定义切点
*/
@Pointcut(value = "@annotation(net.lab1024.smartadmin.common.anno.Lock)")
private void cutMethod() {}
/**
* 切点实现逻辑
* @param point
* @param lock
* @return
* @throws Throwable
*/
@Around(value = "cutMethod() && @annotation(lock)")
public Object parser(ProceedingJoinPoint point, Lock lock) throws Throwable {
String key = lock.key();
boolean restart = lock.restart();
int rCount = lock.rCount();
long waitTime = lock.waitTime();
if (isEl(key)) {
String[] split = key.split("#");
key = split[0] + getByEl("#" + split[1], point);
}
try {
boolean tryLock = redisService.tryLock(key, lock.key(), lock.ttl());
if(tryLock) {
return point.proceed();
} else {
if(restart) {
long increment = redisService.increment(key);
if(increment <= rCount) {
Thread.sleep(waitTime);
return parser(point,lock);
}
}
return ResponseDTO.error("占用中,请稍后再试");
}
} finally {
redisService.delete("icr:" + key);
redisService.unlock(key);
}
}
/**
* 解析 SpEL 表达式并返回其值
*/
private String getByEl(String el, ProceedingJoinPoint point) {
Method method = ((MethodSignature) point.getSignature()).getMethod();
String[] paramNames = getParameterNames(method);
Object[] arguments = point.getArgs();
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(el);
EvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < arguments.length; i++) {
context.setVariable(paramNames[i], arguments[i]);
}
return expression.getValue(context, String.class);
}
/**
* 获取方法参数名列表
*/
private String[] getParameterNames(Method method) {
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
return u.getParameterNames(method);
}
private boolean isEl(String str) {
return str.contains("#");
}
}
利用aop编程,以加了@Lock
的注解为切入点,实现业务逻辑。