Spring Boot微服务中如何实现跨服务异常传递?
Spring Boot微服务间异常传递实现方案
一、基础异常传递:让调用方捕获服务端自定义异常
服务端(提供API的微服务)处理
服务端需要将自定义异常(如ResourceNotFoundException)转换为标准结构化响应,确保异常信息能通过HTTP响应完整传递给调用方:
- 基于你已有的
ApiResponse和CareConnectException结构,用全局异常处理器统一拦截异常:
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity<ApiResponse> handleResourceNotFound(ResourceNotFoundException e) { CareConnectException error = new CareConnectException( "1002", e.getMessage(), "请求的资源不存在,请检查参数" ); ApiResponse response = new ApiResponse(); response.setError(error); // 资源不存在对应HTTP 404状态码更符合REST规范 return new ResponseEntity<>(response, HttpStatus.NOT_FOUND); } }
当服务端抛出ResourceNotFoundException时,会返回包含错误码、消息、提示的结构化响应,而非未处理的原始异常。
调用方(Feign客户端)处理
Feign默认会将非2xx响应包装为FeignException,需要解析响应体还原服务端的异常信息:
- 自定义Feign异常解码器:
@Component public class CustomFeignErrorDecoder implements ErrorDecoder { private final ObjectMapper objectMapper; public CustomFeignErrorDecoder(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } @Override public Exception decode(String methodKey, Response response) { try { // 解析响应体为统一错误格式 ApiResponse apiResponse = objectMapper.readValue(response.body().asInputStream(), ApiResponse.class); CareConnectException error = apiResponse.getError(); // 根据服务端返回的错误码,还原为对应自定义异常 if ("1002".equals(error.getCode())) { return new ResourceNotFoundException("Patient", "Id", error.getMessage()); } return new RuntimeException("服务调用异常:" + error.getMessage()); } catch (IOException e) { // 解析失败时返回默认Feign异常 return FeignException.errorStatus(methodKey, response); } } }
- 给Feign客户端配置该解码器:
@FeignClient(name = "patient-service", configuration = CustomFeignConfig.class) public interface PatientFeignClient { @GetMapping("/patients/{patientId}") ApiResponse getPatientById(@PathVariable Long patientId); } class CustomFeignConfig { @Bean public ErrorDecoder errorDecoder(ObjectMapper objectMapper) { return new CustomFeignErrorDecoder(objectMapper); } }
- 调用方业务代码中直接捕获目标异常:
public PatientEntity getPatient(Long patientId) { try { ApiResponse response = patientFeignClient.getPatientById(patientId); return (PatientEntity) response.getData(); } catch (ResourceNotFoundException e) { // 处理资源不存在逻辑 log.error("获取患者失败:{}", e.getMessage()); throw e; // 或转换为业务异常向上传递 } }
二、多异常场景的优雅处理
如果服务端会抛出多种异常,可通过以下方式优化,避免硬编码大量catch块:
服务端:统一异常分类与编码
定义基础业务异常类,所有自定义异常继承它,全局处理器统一处理:
// 基础业务异常类 public class BaseBusinessException extends RuntimeException { private String errorCode; private String tip; // 构造方法、getter/setter } // 具体异常类 public class ResourceNotFoundException extends BaseBusinessException { public ResourceNotFoundException(String resourceType, String field, String value) { super(String.format("%s with %s=%s not found", resourceType, field, value)); setErrorCode("1002"); setTip("请求的资源不存在,请检查参数"); } } // 全局异常处理器优化 @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(BaseBusinessException.class) public ResponseEntity<ApiResponse> handleBaseBusinessException(BaseBusinessException e) { CareConnectException error = new CareConnectException( e.getErrorCode(), e.getMessage(), e.getTip() ); ApiResponse response = new ApiResponse(); response.setError(error); // 根据异常类型匹配HTTP状态码 HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; if (e instanceof ResourceNotFoundException) { status = HttpStatus.NOT_FOUND; } else if (e instanceof IllegalArgumentException) { status = HttpStatus.BAD_REQUEST; } return new ResponseEntity<>(response, status); } // 处理系统异常 @ExceptionHandler(Exception.class) public ResponseEntity<ApiResponse> handleSystemException(Exception e) { CareConnectException error = new CareConnectException( "9999", e.getMessage(), "系统异常,请稍后重试" ); ApiResponse response = new ApiResponse(); response.setError(error); return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); } }
调用方:通用异常解析与转换
优化Feign解码器,通过错误码映射异常类,避免硬编码:
@Component public class CustomFeignErrorDecoder implements ErrorDecoder { private final ObjectMapper objectMapper; // 错误码-异常类映射,可配置化 private static final Map<String, Class<? extends BaseBusinessException>> ERROR_CODE_MAP = new HashMap<>(); static { ERROR_CODE_MAP.put("1002", ResourceNotFoundException.class); ERROR_CODE_MAP.put("1003", IllegalArgumentException.class); // 新增其他异常映射 } public CustomFeignErrorDecoder(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } @Override public Exception decode(String methodKey, Response response) { try { ApiResponse apiResponse = objectMapper.readValue(response.body().asInputStream(), ApiResponse.class); CareConnectException error = apiResponse.getError(); Class<? extends BaseBusinessException> exceptionClass = ERROR_CODE_MAP.get(error.getCode()); if (exceptionClass != null) { // 反射实例化异常(需保证异常构造方法统一) return exceptionClass.getConstructor(String.class, String.class, String.class) .newInstance(error.getMessage(), error.getCode(), error.getTip()); } else { return new BaseBusinessException(error.getMessage(), error.getCode(), error.getTip()); } } catch (Exception e) { return FeignException.errorStatus(methodKey, response); } } }
调用方只需捕获基础异常类,即可处理所有服务端传递的异常:
public PatientEntity getPatient(Long patientId) { try { ApiResponse response = patientFeignClient.getPatientById(patientId); return (PatientEntity) response.getData(); } catch (BaseBusinessException e) { // 根据异常类型做差异化处理 if (e instanceof ResourceNotFoundException) { // 资源不存在逻辑 } else if (e instanceof IllegalArgumentException) { // 参数错误逻辑 } log.error("服务调用异常,错误码:{},消息:{}", e.getErrorCode(), e.getMessage()); throw e; } }
三、注意事项
- 服务端必须返回结构化错误响应,避免调用方解析混乱。
- 不要在调用方捕获
FeignException后直接抛出新异常,会丢失服务端原始异常信息。 - HTTP状态码需与异常类型匹配,符合REST规范(如资源不存在用404,参数错误用400)。
内容的提问来源于stack exchange,提问作者Himanshu Sharma
相关产品推荐
相关产品推荐

