前言
学习过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 {//分布式锁的keyString 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();//定义分布式锁keyString key = lock.value();if ("".equals(key)){//根据当前的类名+方法参数信息生成keykey = 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")@Transactionalpublic 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