Recover逻辑
首先要说明的是 @Recover 注解并不是一个必须要有的东西,前面我们也分析了,就不再赘述。
但是这个功能用起来确实是不错的,绝大部分异常都应该有对应的兜底措施。
这个东西,就是来执行兜底的动作的。
它的源码也非常容易找到,就紧跟在重试逻辑之后:
下 Debug 几步你就会走到这个地方来:
org.springframework.retry.annotation.RecoverAnnotationRecoveryHandler#recover
又是一个反射调用,这里的 method 已经是 channelNotResp 方法了。
那么问题就来了:Spring-retry 是怎么知道我的重试方法就是 channelNotResp 的呢?
仔细看上面的截图中的 method 对象,不难发现它是方法的第一行代码产生的:
Method method = findClosestMatch(args, cause.getClass());
这个方法从名字和返回值上看叫做找一个最相近的方法。但是具体不太明白啥意思。
跟进去看一眼它在干啥:
这个 Map 里面的 channelNotResp 是什么时候放进去的呢?
很简单,看一下这个 Map 的 put 方法调用的地方就完事了:
就这两个 put 的地方,源码位于下面这个方法中:
org.springframework.retry.annotation.RecoverAnnotationRecoveryHandler#init
从截图中可以看出,这里是在找 class 里面有没有被 @Recover 注解修饰的方法。
我在第 172 行打上断点,调试一下看一下具体的信息,你就知道这里是在干什么了。
在你发起调用之后,程序会在断点处停下,至于是怎么走到这里的,前面说过,看调用堆栈,就不再赘述了。
关于这个 doWith 方法,我们把调用堆栈往上看一步,就知道这里是在解析我们的 RetryService 类里面的所有方法:
当解析到 channelNotResp 方法的时候,会识别出该方法上标注了 @Recover 注解。
但从源码上看,要进行进一步解析,要满足 if 条件。而 if 条件除了要有 Recover 之外,还需要满足这个东西:
method.getReturnType().isAssignableFrom(failingMethod.getReturnType())
isAssignableFrom 方法是判断是否为某个类的父类。
就是的 method 和 failingMethod 分别如下:
这是在检查被 @Retryable 标注的方法和被 @Recover 标注的方法的返回值是否匹配,只有返回值匹配才说明这是一对,应该进行解析。
比如,我把源码改成这样:
当它解析到 channelNotRespStr 方法的时候,会发现虽然被 @Recover 注解修饰了,但是返回值并不一致,从而知道它并不是目标方法 callChannel 的兜底方法。
源码里面的常规套路罢了。
再加入一个 callChannelSrt 方法,在上面的源码中 Spring-retry 就能帮你解析出谁和谁是一对:
接着看一下如果满足条件,匹配上了,if 里面在干啥呢?
这是在获取方法上的入参呀,但是仔细一看,也只是为了获取第一个参数,且这个参数要满足一个条件:
Throwable.class.isAssignableFrom(parameterTypes[0])
必须是 Throwable 的子类,也就说说它必须是一个异常。用 type 字段来承接,然后下面会把它给存起来。
第一次看的时候肯定没看懂这是在干啥,没关系,我看了几次看明白了,给你分享一下,这里是为了这一小节最开始出现的这个方法服务的:
在这里面获取了这个 type,判断如果 type 为 null 则默认为 Throwable.class。
如果有值,就判断这里的 type 是不是当前程序抛出的这个 cause 的同类或者父类。
再强调一遍,从这个方法从名字和返回值上看,我们知道是要找一个最相近的方法,前面我说具体不太明白啥意思都是为了给你铺垫了一大堆 methods 这个 Map 是怎么来的。
其实我心里明镜儿似的,早就想扯下它的面纱了。
来,跟着我的思路马上就能看到葫芦里到底卖的是什么酒了。
你想,findClosestMatch,这个 Closest 是 Close 的最高级,表示最接近的意思。
既然有最接近,那么肯定是有几个东西放在一起,这里面只有一个是最符合要求的。
在源码中,这个要求就是“cause”,就是当前抛出的异常。
而“几个东西”指的就是这个 methods 装的东西里面的 type 属性。
还是有点晕,对不对,别慌,下面这张图片一出来,马上就不晕了: