项目中有许多地方用到定时任务,而定时任务的做法是将定时任务封装成一个服务,采用调接口来实现定时任务的,为防止上次任务未完成,下次任务来临问题,就选择使用Redisson加锁。
一、定义注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.METHOD)
public @interface RedissonTryLockAnnotation {
String lockName();
}
二、定义切面类
@Aspect
@Component
public class RedissonAspect {
private final ExpressionParser parser = new SpelExpressionParser();
private final LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
/** redisson */
private final RedissonClient redissonClient;
public RedissonAspect(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
@Pointcut(value = "@annotation(注解全限定名,如:org.lws.RedissonTryLockAnnotation)")
public void redissonTryLockAspect(){
}
@Around("redissonTryLockAspect()")
public Object aroundMethodTryLock(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
RedissonTryLockAnnotation redissonTryLockAnnotation = method.getAnnotation(RedissonTryLockAnnotation.class);
String lockName = parseSpel(method, point.getArgs(), redissonTryLockAnnotation.lockName(), String.class, redissonTryLockAnnotation.lockName());
RLock rLock = redissonClient.getLock(lockName);
Object result = null;
if (rLock.tryLock()) {
try {
result = point.proceed();
} finally {
rLock.unlock();
}
}
return result;
}
/**
* 解析 spel 表达式
*
* @param method 方法
* @param arguments 参数
* @param spel 表达式
* @param clazz 返回结果的类型
* @param defaultResult 默认结果
* @return 执行spel表达式后的结果
*/
private <T> T parseSpel(Method method, Object[] arguments, String spel, Class<T> clazz, T defaultResult) {
String[] params = discoverer.getParameterNames(method);
if (params != null) {
EvaluationContext context = new StandardEvaluationContext();
for (int len = 0; len < params.length; len++) {
context.setVariable(params[len], arguments[len]);
}
try {
Expression expression = parser.parseExpression(spel);
return expression.getValue(context, clazz);
} catch (Exception e) {
return defaultResult;
}
}
return defaultResult;
}
}
三、使用
需要加锁的方法上加上注解
可以获取到方法中的参数,例如:
@RedissonTryLockAnnotation(lockName = "'" + REDISSON_HANDLE_BLACKLIST + ":' + " + "#execDateTime")
public void handleBlacklist(String execDateTime) {
authService.handleBlacklist(execDateTime);
}
其中使用#execDateTime写法就能读取到方法参数中的execDateTime形参了,同理如果是对象,只需要:
@RedissonTryLockAnnotation(lockName = "'" + REDISSON_IDENT_ISSUE_DEVICE + ":' + " + "#syncIncHashDTO.devId")
public void handleIssueResult(SyncIncHashDTO syncIncHashDTO) {
// 业务逻辑
}
就能获取到syncIncHashDTO里的devId字段了。
全大写的是我自己定义的常量,然后拼接上“:”,目的只是为了给锁一个前缀,好让其在redis中归类,可以根据需要自行定义。