spring boot 全局异常处理无法直接捕获 grpc 连接异常,因为 grpc 客户端在底层使用 netty 或 okhttp 发起请求,其抛出的异常(如 io.grpc.statusruntimeexception)通常不会自动转换为 spring mvc 的 @exceptionhandler 能捕获的 httpmessagenotreadableexception 或标准 http 异常。要捕获这些异常,必须在调用 grpc 服务的代码层进行显式 try-catch,或者通过自定义的 clientinterceptor 统一拦截并转换异常为 spring 可识别的 responseentity 或特定业务异常。

为什么 @RestControllerAdvice 抓不到 gRPC 异常?
因为 gRPC 通信基于 HTTP/2 协议,其错误状态码(如 UNAVAILABLE, DEADLINE_EXCEEDED)被封装在 StatusRuntimeException 中,而不是标准的 HTTP 4xx/5xx 响应体。当 gRPC 客户端抛出此异常时,如果未被当前方法捕获,它会向上传播至 Spring 的 DispatcherServlet,但 DispatcherServlet 默认只处理 Servlet 容器层面的异常,不会自动将 gRPC 异常映射到 JSON 错误响应。因此,必须在服务调用处手动处理。

import io.grpc.StatusRuntimeException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
// 注意:这通常无法直接捕获 gRPC 异常,除非包装了它
@ExceptionHandler(StatusRuntimeException.class)
public ResponseEntity<string> handleGrpcError(StatusRuntimeException ex) {
return ResponseEntity.status(503).body("gRPC Service Unavailable");
}
}</string>
上述代码展示了尝试直接捕获 StatusRuntimeException 的方式,但在实际 Spring Boot Web 应用中,如果 gRPC 调用发生在 Controller 内部且未捕获,该异常往往会导致 500 Internal Server Error 而非预期的 503。更可靠的做法是在 Service 层或 Interceptor 层处理。
如何通过 ClientInterceptor 统一处理 gRPC 异常?
最佳实践是使用 gRPC 的 ClientInterceptor 在客户端发起请求前或接收响应后统一处理异常。通过实现 interceptCall 方法,可以捕获所有 gRPC 调用抛出的 StatusRuntimeException,并将其转换为 Spring 友好的异常或直接返回错误信息。这样可以在不修改每个 Service 调用的情况下,集中管理 gRPC 错误。
import io.grpc.*;
import org.springframework.stereotype.Component;
@Component
public class GrpcErrorHandlerInterceptor implements ClientInterceptor {
@Override
public <reqt respt> ClientCall<reqt respt> interceptCall(
MethodDescriptor<reqt respt> method, CallOptions callOptions, Channel next) {
return new ForwardingClientCall.SimpleForwardingClientCall(next.newCall(method, callOptions)) {
@Override
public void start(Listener<respt> responseListener) {
super.start(new ForwardingClientCallListener.SimpleForwardingClientCallListener(responseListener) {
@Override
public void onClose(Status status, Metadata trailers) {
if (!status.isOk()) {
throw new StatusRuntimeException(status);
}
super.onClose(status, trailers);
}
});
}
};
}
}</respt></reqt></reqt></reqt>
该拦截器会在 gRPC 调用关闭时检查状态,若非成功状态则抛出 StatusRuntimeException。此时,结合前面的 @RestControllerAdvice 即可统一捕获并返回结构化错误响应。确保将此拦截器注册到 ManagedChannel 构建器中。
如何验证 gRPC 连接异常被正确转换为 HTTP 503?
为了验证上述方案的有效性,需要模拟一个 gRPC 服务不可用的场景,例如停止目标 gRPC 服务或设置超时。当客户端尝试调用时,应观察到 HTTP 响应状态码为 503,且响应体包含预定义的错误消息。这证明了从 gRPC 异常到 Spring MVC 响应的转换链路是畅通的。











