You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring @Retryable调用时出现Cannot locate recovery method异常排查

@Retryable注解恢复方法未触发的排查思路

问题概述

调用标注@Retryable的方法时出现Cannot locate recovery method异常,已创建@Recover恢复方法,但重试耗尽后恢复方法未触发。应用已添加@EnableRetry注解,通过故意返回500状态码抛出MyClientException触发重试,但最终仅抛出异常,恢复逻辑未执行。

业务代码

@Retryable(maxAttempts = 3, backoff = @Backoff(random = true, maxDelay = 4000, multiplier = 2.5),
      value = {MyClientException.class})
public ResponseEntity<String> getResponseEntity(MyModel myModel, int retryCount, JsonNode rootNode,String uri) throws MyClientException {

    ResponseEntity<String> response = ResponseEntity.internalServerError().body("ghj"); //故意触发恢复方法
       
    if (!HttpStatus.OK.equals(response.getStatusCode())) {
        log.info("[METHOD] Error while publishing message: {} uri: {}, result: {}, attempt: {}", serviceStatus, uri, response, retryCount);
        throw new MyClientException();
    }
    return response;
}

@Recover
@Counted(value = CustomMetrics.TOTAL_MSG_PROCESSED)
public ResponseEntity<String> recover(Exception e, MyModel myModel) {
    try {
        log.warn("All retries to persist serviceStatus: {} failed. publishing event async as fallback.",
            myModel);
        asyncClient.publish(serviceStatus);
        return ResponseEntity.ok().body("Submitted to persist as async");
    } catch (Exception ex) {
        log.error("Unable to publish event: {} as fallback. Failed with error: {} ",
            serviceStatus, properties.getDeadBucket(), ExceptionUtils.getStackTrace(ex));
        return ResponseEntity.internalServerError().body("Unable to publish");
    }
}

单元测试代码

@Test
public void testRecoverCalledWhenRetryExhausted() throws Exception {
    RetryTemplate retryTemplate = new RetryTemplate();
    RetryPolicy retryPolicy = new SimpleRetryPolicy(3);
    retryTemplate.setRetryPolicy(retryPolicy);

    try {
        retryTemplate.execute(context -> {
            myClientTest.getResponseEntity(new MyModel(), context.getRetryCount(), mock(JsonNode.class), "testUri");
            return null;
        });
    } catch (ExhaustedRetryException e) {
        // 验证恢复方法是否被调用
        verify(MyClientException, times(1)).recover(any(Exception.class), any(myModel.class));
    }
}

日志信息

10:43:29.586 [main] INFO com.----ient -- [[METHOD] Error while publishing message: {"--(hiding data--"} uri: testUri, result: <500 INTERNAL_SERVER_ERROR  Internal Server Error,ghj,[]>, attempt: 0
10:43:29.591 [main] INFO com.-... -- [[METHOD] Error while publishing message: {"--(hiding data--"} uri: testUri, result: <500 INTERNAL_SERVER_ERROR  Internal Server Error,ghj,[]>, attempt: 1
10:43:29.591 [main] INFO com.... -- [METHOD] Error while publishing message: {"--(hiding data--"} uri: testUri, result: <500 INTERNAL_SERVER_ERROR Internal Server Error,ghj,[]>, attempt: 2

MyException()
    at com.wayfair.sct.ocp.status.service.client.services.StatusServiceRestClient.getResponseEntity(StatusServiceRestClient.java:73)
    at com.......MyClientTest.lambda$testRecoverCalledWhenRetryExhausted$0(MyCliemtClientTest.java:131)

排查步骤

  • 参数匹配检查:Spring Retry要求@Recover方法的参数需满足:第一个参数为触发重试的异常类型,后续参数需与原方法的参数按顺序完全匹配(可省略末尾参数,但不能跳过中间参数)。当前恢复方法仅匹配了MyModel,缺少原方法的retryCount、JsonNode、uri参数,导致无法定位恢复方法。需修改恢复方法参数为:
    @Recover
    @Counted(value = CustomMetrics.TOTAL_MSG_PROCESSED)
    public ResponseEntity<String> recover(MyClientException e, MyModel myModel, int retryCount, JsonNode rootNode, String uri) {
        // 原有逻辑
    }
    
  • 异常类型匹配:@Retryable指定的重试异常是MyClientException,恢复方法的第一个参数建议精确使用MyClientException而非父类Exception,避免匹配歧义。
  • 单元测试逻辑修正:手动创建RetryTemplate会绕过Spring Retry的注解驱动机制,@Retryable和@Recover依赖Spring AOP代理生效。测试时需通过Spring容器获取代理后的bean调用方法,而非直接调用或手动用RetryTemplate包裹。
  • AOP代理有效性检查:确保被@Retryable标注的方法所在类被Spring管理(如标注@Service/@Component),且避免类内部自调用(自调用会跳过AOP代理,导致重试和恢复逻辑不生效)。
  • 恢复方法访问权限检查:确认@Recover方法为public修饰符,且与原方法在同一类中(默认仅识别同类中的恢复方法)。

内容的提问来源于stack exchange,提问作者Geek_To_Learn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 05:12:12