详解SpringCloud Finchley Gateway 统一异常处理

SpringCloud Finchley Gateway 统一异常处理

全文搜索[@@]搜索重点内容标记

1 . 问题:使用SpringCloud Gateway时,会出现各种系统级异常,默认返回HTML.

2 . Finchley版本的Gateway,使用WebFlux形式作为底层框架,而不是Servlet容器,所以常规的异常处理无法使用

翻阅源码,默认是使用DefaultErrorWebExceptionHandler这个类实现结构如下:

可以实现参考DefaultErrorWebExceptionHandlerAbstractErrorWebExceptionHandler自定义实现ErrorWebExceptionHandler,然后,注册为Bean到Spring容器中即可(Bean Name:"errorWebExceptionHandler"

具体实现代码如下:

package pro.chenggang.example.spring.cloud.gateway.support;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @classDesc: 统一异常处理,参考{@link org.springframework.web.server.AbstractErrorWebExceptionHandler}修改
 * @author: chenggang
 * @createTime: 2018/10/30
 */
public class JsonExceptionHandler implements ErrorWebExceptionHandler {

 private static final Logger log = LoggerFactory.getLogger(JsonExceptionHandler.class);

 /**
  * MessageReader
  */
 private List<HttpMessageReader<?>> messageReaders = Collections.emptyList();

 /**
  * MessageWriter
  */
 private List<HttpMessageWriter<?>> messageWriters = Collections.emptyList();

 /**
  * ViewResolvers
  */
 private List<ViewResolver> viewResolvers = Collections.emptyList();

 /**
  * 存储处理异常后的信息
  */
 private ThreadLocal<Map<String,Object>> exceptionHandlerResult = new ThreadLocal<>();

 /**
  * 参考AbstractErrorWebExceptionHandler
  * @param messageReaders
  */
 public void setMessageReaders(List<HttpMessageReader<?>> messageReaders) {
  Assert.notNull(messageReaders, "'messageReaders' must not be null");
  this.messageReaders = messageReaders;
 }

 /**
  * 参考AbstractErrorWebExceptionHandler
  * @param viewResolvers
  */
 public void setViewResolvers(List<ViewResolver> viewResolvers) {
  this.viewResolvers = viewResolvers;
 }

 /**
  * 参考AbstractErrorWebExceptionHandler
  * @param messageWriters
  */
 public void setMessageWriters(List<HttpMessageWriter<?>> messageWriters) {
  Assert.notNull(messageWriters, "'messageWriters' must not be null");
  this.messageWriters = messageWriters;
 }

 @Override
 public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
  /**
   * 按照异常类型进行处理
   */
  HttpStatus httpStatus;
  String body;
  if (ex instanceof NotFoundException) {
   httpStatus = HttpStatus.NOT_FOUND;
   body = "Service Not Found";
  }else if(ex instanceof ResponseStatusException) {
   ResponseStatusException responseStatusException = (ResponseStatusException) ex;
   httpStatus = responseStatusException.getStatus();
   body = responseStatusException.getMessage();
  }else{
   httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
   body ="Internal Server Error";
  }
  /**
   * 封装响应体,此body可修改为自己的jsonBody
   */
  Map<String,Object> result = new HashMap<>(2,1);
  result.put("httpStatus",httpStatus);
  result.put("body",body);
  /**
   * 错误记录
   */
  ServerHttpRequest request = exchange.getRequest();
  log.error("[全局异常处理]异常请求路径:{},记录异常信息:{}",request.getPath(),ex.getMessage());
  /**
   * 参考AbstractErrorWebExceptionHandler
   */
  if (exchange.getResponse().isCommitted()) {
   return Mono.error(ex);
  }
  exceptionHandlerResult.set(result);
  ServerRequest newRequest = ServerRequest.create(exchange, this.messageReaders);
  return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse).route(newRequest)
    .switchIfEmpty(Mono.error(ex))
    .flatMap((handler) -> handler.handle(newRequest))
    .flatMap((response) -> write(exchange, response));

 }

 /**
  * 参考DefaultErrorWebExceptionHandler
  * @param request
  * @return
  */
 protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
  Map<String,Object> result = exceptionHandlerResult.get();
  return ServerResponse.status((HttpStatus) result.get("httpStatus"))
    .contentType(MediaType.APPLICATION_JSON_UTF8)
    .body(BodyInserters.fromObject(result.get("body")));
 }

 /**
  * 参考AbstractErrorWebExceptionHandler
  * @param exchange
  * @param response
  * @return
  */
 private Mono<? extends Void> write(ServerWebExchange exchange,
          ServerResponse response) {
  exchange.getResponse().getHeaders()
    .setContentType(response.headers().getContentType());
  return response.writeTo(exchange, new ResponseContext());
 }

 /**
  * 参考AbstractErrorWebExceptionHandler
  */
 private class ResponseContext implements ServerResponse.Context {

  @Override
  public List<HttpMessageWriter<?>> messageWriters() {
   return JsonExceptionHandler.this.messageWriters;
  }

  @Override
  public List<ViewResolver> viewResolvers() {
   return JsonExceptionHandler.this.viewResolvers;
  }

 }
}

注册Bean

 /**
  * 自定义异常处理[@@]注册Bean时依赖的Bean,会从容器中直接获取,所以直接注入即可
  * @param viewResolversProvider
  * @param serverCodecConfigurer
  * @return
  */
 @Primary
 @Bean
 @Order(Ordered.HIGHEST_PRECEDENCE)
 public ErrorWebExceptionHandler errorWebExceptionHandler(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                ServerCodecConfigurer serverCodecConfigurer) {

  JsonExceptionHandler jsonExceptionHandler = new JsonExceptionHandler();
  jsonExceptionHandler.setViewResolvers(viewResolversProvider.getIfAvailable(Collections::emptyList));
  jsonExceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
  jsonExceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());
  log.debug("Init Json Exception Handler Instead Default ErrorWebExceptionHandler Success");
  return jsonExceptionHandler;
 }

[@@]注意事项:

1 .上面为示例代码,其中牵扯到策略工厂和响应封装的类,可以自定义实现

2 .注册Bean时依赖的Bean,都会从Spring容器中获取到

3 .参考此方法思路,可实现统一异常处理,统一封装错误信息。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Spring Cloud Gateway使用Token验证详解

    引入依赖 <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <ty

  • springboot2.0和springcloud Finchley版项目搭建(包含eureka,gateWay,Freign,Hystrix)

    前段时间spring boot 2.0发布了,与之对应的spring cloud Finchley版本也随之而来了,两者之间的关系和版本对应详见我这边文章:spring boot和spring cloud对应的版本关系 项目地址:spring-cloud-demo spring boot 1.x和spring cloud Dalston和Edgware版本搭建的微服务项目现在已经很流行了,现在很多企业都已经在用了,这里就不多说了. 使用版本说明: spring boot 2.0.x spring

  • 详解Spring Cloud Gateway 数据库存储路由信息的扩展方案

    动态路由背景 ​ 无论你在使用Zuul还是Spring Cloud Gateway 的时候,官方文档提供的方案总是基于配置文件配置的方式 例如: # zuul 的配置形式 routes: pig-auth: path: /auth/** serviceId: pig-auth stripPrefix: true # gateway 的配置形式 routes: - id: pigx-auth uri: lb://pigx-auth predicates: - Path=/auth/** filte

  • spring cloud如何修复zuul跨域配置异常的问题

    前言 本文主要给大家介绍一下在zuul进行跨域配置的时候出现异常该如何解决的方法,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 异常 The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed 实例 Access-Control-Allow-Credentials:true Access-Control-Allow-Credentials:t

  • SpringCloud Gateway跨域配置代码实例

    这篇文章主要介绍了SpringCloud Gateway跨域配置代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 Springboot版本:2.1.8.RELEASE SpringCloud版本:Greenwich.SR2 yml配置: spring: cloud: gateway: globalcors: cors-configurations: '[/**]': # 允许携带认证信息 # 允许跨域的源(网站域名/ip),设置*为全部

  • springcloud gateway聚合swagger2的方法示例

    问题描述 在搭建分布式应用时,每个应用通过nacos在网关出装配了路由,我们希望网关也可以将所有的应用的swagger界面聚合起来.这样前端开发的时候只需要访问网关的swagger就可以,而不用访问每个应用的swagger. 框架 springcloud+gateway+nacos+swagger 问题分析 swagger页面是一个单页面应用,所有的显示的数据都是通过和springfox.documentation.swagger.web.ApiResponseController进行数据交互,

  • SpringCloud Finchley Gateway 缓存请求Body和Form表单的实现

    在接入Spring-Cloud-Gateway时,可能有需求进行缓存Json-Body数据或者Form-Urlencoded数据的情况. 由于Spring-Cloud-Gateway是以WebFlux为基础的响应式架构设计,所以在原有Zuul基础上迁移过来的过程中,传统的编程思路,并不适合于Reactor Stream的开发. 网络上有许多缓存案例,但是在测试过程中出现各种Bug问题,在缓存Body时,需要考虑整体的响应式操作,才能更合理的缓存数据 下面提供缓存Json-Body数据或者Form

  • 详解SpringCloud Gateway之过滤器GatewayFilter

    在Spring-Cloud-Gateway之请求处理流程文中我们了解最终网关是将请求交给过滤器链表进行处理,接下来我们阅读Spring-Cloud-Gateway的整个过滤器类结构以及主要功能 通过源码可以看到Spring-Cloud-Gateway的filter包中吉接口有如下三个,GatewayFilter,GlobalFilter,GatewayFilterChain,下来我依次阅读接口的主要实现功能. GatewayFilterChain 类图 代码 /** * 网关过滤链表接口 * 用

  • 详解SpringCloud Finchley Gateway 统一异常处理

    SpringCloud Finchley Gateway 统一异常处理 全文搜索[@@]搜索重点内容标记 1 . 问题:使用SpringCloud Gateway时,会出现各种系统级异常,默认返回HTML. 2 . Finchley版本的Gateway,使用WebFlux形式作为底层框架,而不是Servlet容器,所以常规的异常处理无法使用 翻阅源码,默认是使用DefaultErrorWebExceptionHandler这个类实现结构如下: 可以实现参考DefaultErrorWebExcep

  • 详解使用Spring MVC统一异常处理实战

    1 描述 在J2EE项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的.不可预知的异常需要处理.每个过程都单独处理异常,系统的代码耦合度高,工作量大且不好统一,维护的工作量也很大. 那么,能不能将所有类型的异常处理从各处理过程解耦出来,这样既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护?答案是肯定的.下面将介绍使用Spring MVC统一处理异常的解决和实现过程. 2 分析 Spring MVC处理异常有3种方

  • 详解Spring MVC/Boot 统一异常处理最佳实践

    前言 在 Web 开发中, 我们经常会需要处理各种异常, 这是一件棘手的事情, 对于很多人来说, 可能对异常处理有以下几个问题: 什么时候需要捕获(try-catch)异常, 什么时候需要抛出(throws)异常到上层. 在 dao 层捕获还是在 service 捕获, 还是在 controller 层捕获. 抛出异常后要怎么处理. 怎么返回给页面错误信息. 异常处理反例 既然谈到异常, 我们先来说一下异常处理的反例, 也是很多人容易犯的错误, 这里我们同时讲到前端处理和后端处理 : 捕获异常后

  • 详解SpringCloud新一代网关Gateway

    一.概述简介 1.1.简介 SpringCloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本.而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty. Spring Cloud Gat

  • 详解React 16 中的异常处理

    详解React 16 中的异常处理 异常处理 在 React 15.x 及之前的版本中,组件内的异常有可能会影响到 React 的内部状态,进而导致下一轮渲染时出现未知错误.这些组件内的异常往往也是由应用代码本身抛出,在之前版本的 React 更多的是交托给了开发者处理,而没有提供较好地组件内优雅处理这些异常的方式.在 React 16.x 版本中,引入了所谓 Error Boundary 的概念,从而保证了发生在 UI 层的错误不会连锁导致整个应用程序崩溃:未被任何异常边界捕获的异常可能会导致

  • 详解Flask开发技巧之异常处理

    目录 一.Flask内置异常处理 二.HTTPException类分析 三.自定义异常处理类 四.方便的定义自己的错误类 五.注意事项 一.Flask内置异常处理 要想在Flask中处理好异常,有一套自己的异常处理机制,首先,我们必须先知道Flask自己是如何处理异常的.去flask的源码里找一找会发现,在flask源码的app.py文件下,有很多会抛出异常的方法,其中拿一个举例: def handle_exception(self, e): """Default excep

  • 详解SpringBoot如何实现统一后端返回格式

    目录 1.为什么要对SpringBoot返回统一的标准格式 1.1 返回String 1.2 返回自定义对象 1.3 接口异常 2.定义返回对象 3.定义状态码 4.统一返回格式 5.高级实现方式 5.1 ResponseBodyAdvice的源码 5.2 String类型判断 在前后端分离的项目中后端返回的格式一定要友好,不然会对前端的开发人员带来很多的工作量.那么SpringBoot如何做到统一的后端返回格式呢?今天我们一起来看看. 1.为什么要对SpringBoot返回统一的标准格式 在默

  • 详解SpringCloud服务认证(JWT)

     - JWT JWT(JSON Web Token), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景.JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密. - JWT与其它的区别 通常情况下,把API直接暴露出去是风险很大的,不说别的

  • 详解SpringCloud的负载均衡

    目录 一.什么是负载均衡 二.负载均衡的简单分类 三.为什么需要做负载均衡 四.springCloud如何开启负载均衡 五.IRule 1.RandomRule:表示随机策略,它将从服务清单中随机选择一个服务: 2.ClientConfigEnabledRoundRobinRule:ClientConfigEnabledRoundRobinRule并没有实现什么特殊的处理逻辑,但是他的子类可以实现一些高级策略, 当一些本身的策略无法实现某些需求的时候,它也可以做为父类帮助实现某些策略,一般情况下

  • 详解SpringCloud微服务之Rest

    目录 一.什么是RestTemplate? 二.四种请求方式 2.1 GET请求 2.2 POST请求 2.3 PUT请求 2.4 DELETE请求 一.什么是RestTemplate? RestTemplate 是一个HTTP客户端,在Spring Cloud的服务调用方使用它我们可以方便的调用HTTP接口,支持GET.POST.PUT.DELETE等方法. 二.四种请求方式 首先注入Bean对象 @Configuration public class MyConfig { @Bean pub

随机推荐