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

Resilience4j降级方法引发类型转换异常,寻求修复方案

Resilience4j容错触发时的类型不匹配问题解决

问题代码

主方法(带Resilience4j注解)

@Bulkhead(name = "getToken", type = Bulkhead.Type.THREADPOOL)
@TimeLimiter(name = "getToken")
@CircuitBreaker(name = "getToken", fallbackMethod = "fallbackRequestException")
public ResponseEntity<Resources> getByTokenPayload(HttpHeaders headers, String url) {
    return CompletableFuture.supplyAsync(() -> {
        ResponseEntity<Resources> result = service.getByTokenResource(headers, url);
    }).thenApply(result -> {
        return result;
    });
}

降级方法

private CompletableFuture<ResponseEntity<?>> fallbackRequestException(HttpHeaders headers, String paymentHandleToken, Throwable e) {
    // Create a body with error details
    Map<String, Object> errorBody = new HashMap<>();
    errorBody.put("error", "Fallback triggered due to an exception");
    errorBody.put("message", e.getMessage());
    errorBody.put("paymentHandleToken", paymentHandleToken);
    errorBody.put("timestamp", Instant.now());

    ResponseEntity<?> responseEntity = new ResponseEntity<>(errorBody, headers, HttpStatus.INTERNAL_SERVER_ERROR);

    // Return a CompletableFuture with the ResponseEntity
    return CompletableFuture.completedFuture(responseEntity);
}

触发异常时的错误日志

org.springframework.retry.ExhaustedRetryException: Cannot locate recovery method
    at org.springframework.retry.annotation.RecoverAnnotationRecoveryHandler.recover(RecoverAnnotationRecoveryHandler.java:78) ~[spring-retry-2.0.6.jar!/:na]

2024-10-03T18:09:06.165Z ERROR 1 --- [service] [nio-8180-exec-5] c.a.p.c.e.w.h.CommonControllerAdvice     : Caught unexpected exception:

java.lang.ClassCastException: class java.util.HashMap cannot be cast to class com.test.Resources (java.util.HashMap is in module java.base of loader 'bootstrap'; com.test.Resources is in unnamed module of loader org.springframework.boot.loader.launch.LaunchedClassLoader @f6f4d33)
    at com.test.TransactionDetailsService.getDetails(Service.java:212) ~[!/:0.0.1]

问题根源

  1. 返回类型不匹配导致降级方法找不到:主方法返回ResponseEntity<Resources>,但降级方法返回CompletableFuture<ResponseEntity<?>>,Resilience4j无法匹配到对应的降级方法,抛出Cannot locate recovery method。
  2. 异步返回值被错误同步接收:主方法内部返回CompletableFuture,但方法声明返回ResponseEntity<Resources>,实际返回的是CompletableFuture对象,调用方后续强转成Resources时抛出ClassCastException。
  3. 参数不匹配:降级方法的第二个参数是paymentHandleToken,但主方法的第二个参数是url,参数名称不匹配也会导致降级方法无法被识别。

解决方案

1. 修正主方法的返回类型与实现

使用@TimeLimiter注解的方法必须返回CompletableFuture类型,否则注解不生效且会引发类型错误。调整主方法如下:

@Bulkhead(name = "getToken", type = Bulkhead.Type.THREADPOOL)
@TimeLimiter(name = "getToken")
@CircuitBreaker(name = "getToken", fallbackMethod = "fallbackRequestException")
public CompletableFuture<ResponseEntity<Resources>> getByTokenPayload(HttpHeaders headers, String url) {
    return CompletableFuture.supplyAsync(() -> service.getByTokenResource(headers, url));
}

2. 修正降级方法的参数与返回类型

降级方法需要满足:

  • 参数列表与主方法完全一致,最后追加Throwable参数;
  • 返回类型与主方法完全匹配。

方案A:返回符合主方法类型的对象

如果业务允许,构造Resources对象返回:

private CompletableFuture<ResponseEntity<Resources>> fallbackRequestException(HttpHeaders headers, String url, Throwable e) {
    Resources errorResource = new Resources();
    errorResource.setError("Fallback triggered due to an exception");
    errorResource.setMessage(e.getMessage());
    errorResource.setTimestamp(Instant.now());
    
    ResponseEntity<Resources> response = new ResponseEntity<>(errorResource, headers, HttpStatus.INTERNAL_SERVER_ERROR);
    return CompletableFuture.completedFuture(response);
}

方案B:统一返回泛型类型(允许返回错误Map)

如果需要返回错误Map,将主方法的返回类型改为CompletableFuture<ResponseEntity<?>>,同时调整降级方法:

// 主方法修改返回类型
@Bulkhead(name = "getToken", type = Bulkhead.Type.THREADPOOL)
@TimeLimiter(name = "getToken")
@CircuitBreaker(name = "getToken", fallbackMethod = "fallbackRequestException")
public CompletableFuture<ResponseEntity<?>> getByTokenPayload(HttpHeaders headers, String url) {
    return CompletableFuture.supplyAsync(() -> service.getByTokenResource(headers, url));
}

// 降级方法保持实现,但参数要匹配主方法
private CompletableFuture<ResponseEntity<?>> fallbackRequestException(HttpHeaders headers, String url, Throwable e) {
    Map<String, Object> errorBody = new HashMap<>();
    errorBody.put("error", "Fallback triggered due to an exception");
    errorBody.put("message", e.getMessage());
    errorBody.put("url", url);
    errorBody.put("timestamp", Instant.now());

    ResponseEntity<?> responseEntity = new ResponseEntity<>(errorBody, headers, HttpStatus.INTERNAL_SERVER_ERROR);
    return CompletableFuture.completedFuture(responseEntity);
}

3. 关键注意事项

  • @TimeLimiter强制要求方法返回CompletableFuture,否则注解逻辑不执行,还会导致返回值类型混乱;
  • 降级方法的参数必须和主方法入参完全一致(顺序、类型),最后加Throwable,参数名称也建议保持一致;
  • 调用主方法的地方需要处理CompletableFuture的异步结果,避免直接强转同步对象。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 18:44:54