ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、视频、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## 一、为什么需要网关层面的全局异常处理 下图表示的是一次请求,经由网关转发微服务并由微服务操作数据库的一次请求处理流程。在请求处理过程中包含5处可能出现异常的位置 1. 请求到达网关,网关处理请求发生异常 2. 网关进行请求转发到微服务,转发过程中服务发现异常或网络异常 3. 微服务处理请求,请求处理过程发生异常 4. 微服务调用操作数据库,数据库操作异常 5. 数据库本身发生网络或其他异常 ![](https://img.kancloud.cn/8f/6b/8f6b37618786ca6d6f841d8ce5073372_1200x378.png) 对于3、4、5类的异常,微服务通过`ControllerAdvice + ExceptionHandler`进行全局异常处理,返回全局通用的请求响应数据结构。(对于服务层面的全局异常处理,可以参考我写的另一本参考文档:[《手摸手教你学Spring Boot2.0》](https://www.kancloud.cn/hanxt/springboot2/content))。 如果不进行全局的异常处理,Spring Boot会默认响应一个WhiteLabel Error Page,这样的响应结果很不友好。 ![](https://img.kancloud.cn/18/e8/18e83fa8d4ce75515bdf8fba5f8bbea7_665x144.png) 对于1、2类的异常如果我们不进行统一的处理,默认的响应方式和Spring Boot是一样的,很不友好。所以也需要在网关层面进行全局的异常处理,这样对于网关本身出现的异常和请求转发过程的异常,也能给用户一个比较友好的数据响应结果,对于异常信息本身有一个合理的日志记录。 ## 二、源码分析 那我们该如何实现网关层面的全局异常处理呢?先不着急做,我们先来看一下GateWay默认是怎么处理的,先看ExceptionHandlingWebHandler ~~~ public class ExceptionHandlingWebHandler extends WebHandlerDecorator { //持有若干的WebExceptionHandler private final List<WebExceptionHandler> exceptionHandlers; public ExceptionHandlingWebHandler(WebHandler delegate, List<WebExceptionHandler> handlers) { super(delegate); this.exceptionHandlers = Collections.unmodifiableList(new ArrayList<>(handlers)); } public List<WebExceptionHandler> getExceptionHandlers() { return this.exceptionHandlers; } @Override public Mono<Void> handle(ServerWebExchange exchange) { Mono<Void> completion; try { completion = super.handle(exchange); }catch (Throwable ex) { completion = Mono.error(ex); } //当出现异常的时候onErrorResume,使用WebExceptionHandler进行异常处理 for (WebExceptionHandler handler : this.exceptionHandlers) { completion = completion.onErrorResume(ex -> handler.handle(exchange, ex)); } return completion; } } ~~~ 通过上面的代码,我们知道WebExceptionHandler是异常处理类,我们来看一下它的代码 ![](https://img.kancloud.cn/3a/ef/3aef36a9d7419b6f1dd24f3157c48097_1247x504.png) WebExceptionHandler是一个接口,其默认生效的实现类是DefaultErrorWebExceptionHandler,其默认的处理是渲染为error html页面。 ~~~ @Override protected RouterFunction<ServerResponse> getRoutingFunction( ErrorAttributes errorAttributes) { return route(acceptsTextHtml(), this::renderErrorView).andRoute(all(), this::renderErrorResponse); } ~~~ ## 三、自定义全局异常处理 通过上面的分析,我们知道:如果我们希望在网关层面进行全局的异常处理,可以实现WebExceptionHandler接口。 但在实际使用中,我们通常去实现ErrorWebExceptionHandler,ErrorWebExceptionHandler继承自WebExceptionHandler。 ~~~ package org.springframework.boot.web.reactive.error; import org.springframework.web.server.WebExceptionHandler; @FunctionalInterface public interface ErrorWebExceptionHandler extends WebExceptionHandler { } ~~~ ErrorWebExceptionHandler是一个函数式接口,我们只需要实现其handle方法,就可以实现全局异常处理。 ~~~ @Slf4j @Order(-1) @Component @RequiredArgsConstructor public class JsonExceptionHandler implements ErrorWebExceptionHandler { @Override public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) { ServerHttpResponse response = exchange.getResponse(); if (response.isCommitted()) { //对于已经committed(提交)的response,就不能再使用这个response向缓冲区写任何东西 return Mono.error(ex); } // header set 响应JSON类型数据,统一响应数据结构(适用于前后端分离JSON数据交换系统) response.getHeaders().setContentType(MediaType.APPLICATION_JSON); // 按照异常类型进行翻译处理,翻译的结果易于前端理解 String message; if (ex instanceof NotFoundException) { response.setStatusCode(HttpStatus.NOT_FOUND); message = "您请求的服务不存在"; } else if (ex instanceof ResponseStatusException) { ResponseStatusException responseStatusException = (ResponseStatusException) ex; response.setStatusCode(responseStatusException.getStatus()); message = responseStatusException.getMessage(); } else if (ex instanceof GateWayException) { response.setStatusCode(HttpStatus.FORBIDDEN); message = ex.getMessage(); } else { response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); message = "服务器内部错误"; } //全局通用响应数据结构,可以自定义。通常包含请求结果code、message、data AjaxResponse result = AjaxResponse.error( response.getStatusCode().value(), message); writeLog(exchange, ex); return response.writeWith(Mono.fromSupplier(() -> { DataBufferFactory bufferFactory = response.bufferFactory(); return bufferFactory.wrap(JSON.toJSONBytes(result)); })); } //将错误信息以日志的形式记录下来 private void writeLog(ServerWebExchange exchange, Throwable ex) { ServerHttpRequest request = exchange.getRequest(); URI uri = request.getURI(); String host = uri.getHost(); int port = uri.getPort(); log.error("[gateway]-host:{} ,port:{},url:{}, errormessage:", host, port, request.getPath(), ex); } } ~~~