前言
关于分布式锁的相关介绍,这边文章就不多阐述了.可以看上篇文章《电商项目 - 分布式锁实现 - 编程式》原理基本大同小异。
这边文章主要是介绍使用注解+AOP+Redisson实现分布式锁声明式
实现思路
思路: 使用环绕通知,在执行前加锁,执行后释放锁
代码实现
被@Lock注解标识的方法表示加锁
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface Lock {
String value();
String param() default "";
long waitTime() default 30L;
long leaseTime() default 100L;
TimeUnit timeUnit() default TimeUnit.SECONDS;
LockType type() default LockType.FAIR;
}
Redisson提供了可重入锁、公平锁机制,我们通过LockType来标示
/**
* 锁类型
*/
public enum LockType {
/**
* 可重入锁
*/
REENTRANT,
/**
* 公平锁
*/
FAIR
}
我们不直接使用Redisson,而是抽象一个接口,由实现类去实现使用Redisson加锁。目的通过面向接口编程实现解耦,下次底层不想使用Redisson作为底层实现可以另写一个类实现接口使用其他底层实现,方便拓展与维护。
public interface RedisLockClient {
boolean tryLock(String lockName, LockType lockType, long waitTime, long leaseTime, TimeUnit timeUnit) throws InterruptedException;
void unLock(String lockName, LockType lockType);
<T> T lock(String lockName, LockType lockType, long waitTime, long leaseTime, TimeUnit timeUnit, CheckedSupplier<T> supplier);
default <T> T lockFair(String lockName, long waitTime, long leaseTime, CheckedSupplier<T> supplier) {
return this.lock(lockName, LockType.FAIR, waitTime, leaseTime, TimeUnit.SECONDS, supplier);
}
default <T> T lockReentrant(String lockName, long waitTime, long leaseTime, CheckedSupplier<T> supplier) {
return this.lock(lockName, LockType.REENTRANT, waitTime, leaseTime, TimeUnit.SECONDS, supplier);
}
}
实现RedisLockClient
@Slf4j
public class RedisLockClientImpl implements RedisLockClient {
private final RedissonClient redissonClient;
@Override
public boolean tryLock(String lockName, LockType lockType, long waitTime, long leaseTime, TimeUnit timeUnit) throws InterruptedException {
RLock lock = this.getLock(lockName, lockType);
return lock.tryLock(waitTime, leaseTime, timeUnit);
}
@Override
public void unLock(String lockName, LockType lockType) {
RLock lock = this.getLock(lockName, lockType);
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
private RLock getLock(String lockName, LockType lockType) {
RLock lock;
if (LockType.REENTRANT == lockType) {
lock = this.redissonClient.getLock(lockName);
} else {
lock = this.redissonClient.getFairLock(lockName);
}
return lock;
}
@Override
public <T> T lock(String lockName, LockType lockType, long waitTime, long leaseTime, TimeUnit timeUnit, CheckedSupplier<T> supplier) {
T o;
try {
boolean result = this.tryLock(lockName, lockType, waitTime, leaseTime, timeUnit);
if (!result) {
throw new RuntimeException("业务繁忙!请稍后再试!");
}
o = supplier.get();
} catch (RuntimeException e) {
throw e;
} catch (Throwable throwable) {
throwable.printStackTrace();
throw new RuntimeException("系统异常");
} finally {
this.unLock(lockName, lockType);
}
return o;
}
public RedisLockClientImpl(final RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
}
CheckedSupplier用于传递ProceedingJoinPoint
@FunctionalInterface
public interface CheckedSupplier<T> {
T get() throws Throwable;
}
EL表达式解析器(关于解析EL表达式不熟悉的可以看看《通过AOP+Java注解+EL表达式获取方法参数的值》)
public class ExpressionEvaluator<T> extends CachedExpressionEvaluator {
private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer();
private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<>(64);
private final Map<AnnotatedElementKey, Method> targetMethodCache = new ConcurrentHashMap<>(64);
public EvaluationContext createEvaluationContext(Object object, Class<?> targetClass, Method method, Object[] args) {
Method targetMethod = getTargetMethod(targetClass, method);
ExpressionRootObject root = new ExpressionRootObject(object, args);
return new MethodBasedEvaluationContext(root, targetMethod, args, this.paramNameDiscoverer);
}
public T condition(String conditionExpression, AnnotatedElementKey elementKey, EvaluationContext evalContext, Class<T> clazz) {
return getExpression(this.conditionCache, elementKey, conditionExpression).getValue(evalContext, clazz);
}
private Method getTargetMethod(Class<?> targetClass, Method method) {
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
Method targetMethod = this.targetMethodCache.get(methodKey);
if (targetMethod == null) {
targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
if (targetMethod == null) {
targetMethod = method;
}
this.targetMethodCache.put(methodKey, targetMethod);
}
return targetMethod;
}
}
public class ExpressionRootObject {
private final Object object;
private final Object[] args;
public ExpressionRootObject(Object object, Object[] args) {
this.object = object;
this.args = args;
}
public Object getObject() {
return object;
}
public Object[] getArgs() {
return args;
}
}
重点来啦,使用AspectJ的环绕通知来实现加锁/解锁
@Aspect
@Slf4j
@Component
public class RedisLockAspect {
private static final ExpressionEvaluator<String> EVALUATOR = new ExpressionEvaluator<>();
private final RedisLockClient redisLockClient;
@Around("@annotation(redisLock)")
public Object aroundRedisLock(ProceedingJoinPoint point, Lock redisLock) {
log.info("======================into lock=======================");
String lockName = redisLock.value();
Assert.hasText(lockName, "@Lock value must have length; it must not be null or empty");
String lockParam = redisLock.param();
String lockKey;
if (StringUtils.isNotBlank(lockParam)) {
String evalAsText = this.evalLockParam(point, lockParam);
lockKey = lockName + ':' + evalAsText;
} else {
lockKey = lockName;
}
LockType lockType = redisLock.type();
long waitTime = redisLock.waitTime();
long leaseTime = redisLock.leaseTime();
TimeUnit timeUnit = redisLock.timeUnit();
return this.redisLockClient.lock(lockKey, lockType, waitTime, leaseTime, timeUnit, point::proceed);
}
/**
* 解析EL表达式
* @param point 切入点
* @param lockParam 需要解析的EL表达式
* @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.createEvaluationContext(target, target.getClass(), method, args);
AnnotatedElementKey elementKey = new AnnotatedElementKey(method, targetClass);
return EVALUATOR.condition(lockParam, elementKey, context, String.class);
}
public RedisLockAspect(final RedisLockClient redisLockClient) {
this.redisLockClient = redisLockClient;
}
}
最后在需要加锁的方法上加上@Lock即可
@Lock("lock-pay")
@PostMapping("pay")
public ResultVo pay(){
// 支付流程
return ResultVo.success();
}
以上就完成了分布式锁实现 - 声明式,实现起来并不难,主要得有思路。