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]
问题根源
- 返回类型不匹配导致降级方法找不到:主方法返回
ResponseEntity<Resources>,但降级方法返回CompletableFuture<ResponseEntity<?>>,Resilience4j无法匹配到对应的降级方法,抛出Cannot locate recovery method。 - 异步返回值被错误同步接收:主方法内部返回
CompletableFuture,但方法声明返回ResponseEntity<Resources>,实际返回的是CompletableFuture对象,调用方后续强转成Resources时抛出ClassCastException。 - 参数不匹配:降级方法的第二个参数是
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
相关产品推荐
相关产品推荐

