前言
学习过Spring的小伙伴都知道AOP的强大,本文将通过Redisson结合AOP,仅需一个注解就能实现分布式锁。 🍭
不会使用aop和redisson的小伙伴可以参考:
【学习总结】使Aop实现自定义日志注解-CSDN博客
【学习总结】使用分布式锁和乐观锁解决“超卖”问题-CSDN博客
前提
有小伙伴可能会看不懂下面对key的一些操作,当key为null时,使用StringBuilder手动拼接key,不为null时,主要使用到了SpEl表达式。
- 使用Spring Expression Language (SpEL)来支持在`@DLock`注解的`value`属性中定义动态key。
- 使用`StandardEvaluationContext`和`DefaultParameterNameDiscoverer`来解析方法参数名,并将它们作为变量存储在SpEL的上下文中。
- 使用`SpelExpressionParser`来解析key字符串中的SpEL表达式,并获取最终的key值。
代码
自定义注解:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DLock {
//分布式锁的key
String value() default "";
}
value属性定义了分布式锁的key,默认为空,如果为空会根据当前注解的类及方法参数等生成key。
切面定义:
@Component
@Aspect
public class DistributedLockAspect {
private final RedissonClient redisson;
public DistributedLockAspect(RedissonClient redissonClient){
this.redisson = redissonClient;;
}
//定义切面
@Pointcut("@annotation(lock)")
private void lockPointcut(DLock lock){}
@Around("lockPointcut(lock)")
public Object lockAround(ProceedingJoinPoint point,DLock lock) throws Throwable{
//从ProceedingJoinPoint对象中获取目标方法的签名,并将其强制转换为MethodSignature类型
MethodSignature signature = (MethodSignature)point.getSignature();
//获取目标对象的方法
Method method = signature.getMethod();
//获取目标方法的参数数组
Object[] args = point.getArgs();
//定义分布式锁key
String key = lock.value();
if ("".equals(key)){
//根据当前的类名+方法参数信息生成key
key = configKey(signature.getDeclaringType(), method).replaceAll("[^a-zA-Z0-9]", "") ;
System.out.println(key);
}else {
//支持SpEL表达式
StandardEvaluationContext context = new StandardEvaluationContext();
//将当前方法参数信息都存入到SpEl执行的上下文中
DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(method);
for (int i = 0,len = parameterNames.length; i < len; i++){
context.setVariable(parameterNames[i],args[i]);
}
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(key);
key = expression.getValue(context,String.class);
}
//加锁
RLock rLock = redisson.getLock(key);
rLock.lock();
try {
//执行业务代码
Object o = point.proceed();
return o;
}finally {
rLock.unlock();
}
}
private String configKey(Class<?> targetType, Method method) {
StringBuilder builder = new StringBuilder();
builder.append(targetType.getSimpleName());
builder.append('#').append(method.getName()).append('(');
for (Class<?> param : method.getParameterTypes()){
builder.append(param.getSimpleName()).append(',');
}
if (method.getParameterTypes().length > 0){
builder.deleteCharAt(builder.length() - 1);
}
return builder.append(')').toString();
}
}
以上是基于注解实现分布式锁的核心类都定义完成了,接下来进行测试。
@GetMapping("/stock")
@DLock("'user:' + #userId + ':' + #productId")
@Transactional
public String decStocks(@RequestParam Long userId, @RequestParam Integer productId){
//查询商品信息
Products product = productsService.getById(productId);
//获取商品库存
Integer stockQuantity = product.getStockQuantity();
if (stockQuantity > 0){
UpdateWrapper<Products> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id",productId).setSql("stock_quantity = stock_quantity - 1");
boolean result = productsService.update(updateWrapper);
if (result){
return "商品库存呢扣减成功";
}
}
return "商品卖完了!";
}
我就以一个简单的商品超卖的例子进行测试
假设库存为10
使用JMeter进行测试
测试结果
没有出现库存为负数的情况,非常成功,说明我们的锁注解起作用了。
总结
有兴趣的小伙伴可以试一试。
参考文章:
https://mp.weixin.qq.com/s/Bkhg74dE9HilE7PFtqj-5whttps://mp.weixin.qq.com/s/Bkhg74dE9HilE7PFtqj-5w