扒一扒@Retryable注解,很优雅,有点意思! (2)

简介: 扒一扒@Retryable注解,很优雅,有点意思! (2)

翻源码


源码之下无秘密。

首先我们看一下前面找到的 Debug 入口:

org.springframework.retry.support.RetryTemplate#doExecute

从日志里面可以直观的看出,这个方法里面肯定就包含我要找的 for 循环。

但是...

很遗憾,并不是 for 循环,而是一个 while 循环。问题不大,意思差不多:

image.png

打上断点,然后把项目跑起来,跑到断点的地方我最关心的是下面的调用堆栈:

image.png

被框起来了两部分,一部分是 spring-aop 包里面的内容,一部分是 spring-retry。

然后我们看到 spring-retry 相关的第一个方法:

image.png

恭喜你,如果说前面通过日志找到了第一个打断点的位置,那么通过第一个断点的调用堆栈,我们找到了整个 retry 最开始的入口处,另外一个断点就应该打在下面这个方法的入口处:

org.springframework.retry.annotation.AnnotationAwareRetryOperationsInterceptor#invoke


image.png

说真的,观察日志加调用栈这个最简单的组合拳用好了,调试绝大部分源码的过程中都不会感觉特别的乱。

找到了入口了,我们就从接口处接着看源码。

这个 invoke 方法一进来首先是试着从缓存中获取该方法是否之前被成功解析过,如果缓存中没有则解析当前调用的方法上是否有 @Retryable 注解。

如果是被 @Retryable 修饰的,返回的 delegate 对象则不会是 null。所以会走到 retry 包的代码逻辑中去。

image.png

然后在 invoke 这里有个小细节,如果 recoverer 对象不为空,则执行带回调的。如果为空则执行没有 recoverCallback 对象方法。

我看到这几行代码的时候就大胆猜测: @Recover 注解并不是必须的。

于是我兴奋的把这个方法注解掉并再次运行项目,发现还真是,有点不一样了:

image.png

在我没有看其他文章、没有看官方介绍,仅通过一个简单的示例就发掘到他的一个用法之后,这属于意外收获,也是看源码的一点小乐趣。

其实源码并没有那么可怕的。

image.png

但是看到这里的时候另外一个问题就随之而来了:

这个 recoverer 对象看起来就是我写的 channelNotResp 方法,但是它是在什么时候解析到的呢?

image.png

按下不表,后面再说,当务之急是找到重试的地方。

在当前的这个方法中再往下走几步,很快就能到我前面说的 while 循环中来:

image.png

主要关注这个 canRetry 方法:

org.springframework.retry.RetryPolicy#canRetry

点进去之后,发现是一个接口,拥有多个实现:

image.png

简单的介绍一下其中的几种含义是啥:

  • AlwaysRetryPolicy:允许无限重试,直到成功,此方式逻辑不当会导致死循环
  • NeverRetryPolicy:只允许调用RetryCallback一次,不允许重试
  • SimpleRetryPolicy:固定次数重试策略,默认重试最大次数为3次,RetryTemplate默认使用的策略
  • TimeoutRetryPolicy:超时时间重试策略,默认超时时间为1秒,在指定的超时时间内允许重试
  • ExceptionClassifierRetryPolicy:设置不同异常的重试策略,类似组合重试策略,区别在于这里只区分不同异常的重试
  • CircuitBreakerRetryPolicy:有熔断功能的重试策略,需设置3个参数openTimeout、resetTimeout和delegate
  • CompositeRetryPolicy:组合重试策略,有两种组合方式,乐观组合重试策略是指只要有一个策略允许即可以重试,悲观组合重试策略是指只要有一个策略不允许即不可以重试,但不管哪种组合方式,组合中的每一个策略都会执行

那么这里问题又来了,我们调试源码的时候这么有多实现,我怎么知道应该进入哪个方法呢?

记住了,接口的方法上也是可以打断点的。你不知道会用哪个实现,但是 idea 知道:

image.png

这里就是用的 SimpleRetryPolicy 策略,即这个策略是 Spring-retry 的默认重试策略。

t == null || retryForException(t)) && context.getRetryCount() < this.maxAttempts

这个策略的逻辑也非常简单:

  • 1.如果有异常,则执行 retryForException 方法,判断该异常是否可以进行重试。
  • 2.判断当前已重试次数是否超过最大次数。

在这里,我们找到了控制重试逻辑的地方。

上面的第二点很好理解,第一点说明这个注解和事务注解 @Transaction 一样,是可以对指定异常进行处理的,可以看一眼它支持的选项:

image.png

注意 include 里面有句话我标注了起来,意思是说,这个值默认为空。且当 exclude 也为空时,默认是所有异常。

所以 Demo 里面虽然什么都没配,但是抛出 TimeoutException 也会触发重试逻辑。

又是一个通过翻源码挖掘到的知识点,这玩意就像是探索彩蛋似的,舒服。

看完判断是否能进行重试调用的逻辑之后,我们接着看一下真正执行业务方法的地方:

org.springframework.retry.RetryCallback#doWithRetry

目录
相关文章
com.alibaba.excel包教程:Excel数据导出加工进阶篇
com.alibaba.excel包教程:Excel数据导出加工进阶篇
2722 0
|
安全 Java
HashMap和Hashtable的区别
HashMap和Hashtable的区别
558 2
|
XML Java UED
使用 Spring Boot 实现重试和补偿功能:从理论到实践
【6月更文挑战第17天】在分布式系统中,服务之间的调用可能会因为网络故障、服务器负载等原因偶尔失败。为了提高系统的可靠性和稳定性,我们经常需要实现重试和补偿功能。
724 6
|
11月前
|
前端开发 小程序 Java
uniapp-网络数据请求全教程
这篇文档介绍了如何在uni-app项目中使用第三方包发起网络请求
826 3
|
缓存 监控 Java
如何在 Java 8 中创建和使用线程池?
【7月更文挑战第8天】
931 9
如何在 Java 8 中创建和使用线程池?
|
Java Spring
Spring Boot使用策略模式指定Service实现类
Spring Boot使用策略模式指定Service实现类
356 0
|
人工智能 自动驾驶 算法
AIoT(人工智能物联网)技术的发展前景
【8月更文挑战第1天】AIoT技术作为人工智能与物联网的深度融合产物,正展现出广阔的发展前景。随着技术的不断进步和市场需求的持续增长,AIoT技术将在更多领域和场景发挥重要作用,推动社会向更加智能化、高效化的方向发展。
1103 6
|
网络协议 物联网
|
Java 领域建模 开发者
框架来解决优雅重试-spring retry
重试的意义To make processing more robust and less prone to failure, it sometimes helps to automatically retry a failed operation, in case it might succeed on a subsequent attempt. Errors that are susceptib
3035 0
框架来解决优雅重试-spring retry
|
安全 数据安全/隐私保护 开发者
如何使用Pyarmor保护你的Python脚本
如何使用Pyarmor保护你的Python脚本
778 0