相关文章推荐
俊逸的海豚  ·  Failure in ...·  4 天前    · 
豪爽的小狗  ·  StreamingResponseBody的 ...·  2 天前    · 
侠义非凡的剪刀  ·  python - ...·  1 年前    · 
文雅的莴苣  ·  NAS上的Docker+NextCloud方 ...·  1 年前    · 
愤怒的伤痕  ·  javascript - ...·  1 年前    · 

我们平时在用 SpringMVC 的时候,只要是经过 DispatcherServlet 处理的请求,可以通过 @ControllerAdvice @ExceptionHandler 自定义不同类型异常的处理逻辑,具体可以参考 ResponseEntityExceptionHandler DefaultHandlerExceptionResolver ,底层原理很简单,就是发生异常的时候搜索容器中已经存在的异常处理器并且匹配对应的异常类型,匹配成功之后使用该指定的异常处理器返回结果进行 Response 的渲染,如果找不到默认的异常处理器则用默认的进行兜底(个人认为,Spring在很多功能设计的时候都有这种“有则使用自定义,无则使用默认提供”这种思想十分优雅)。

SpringMVC 中提供的自定义异常体系在 Spring-WebFlux 中并不适用,其实原因很简单,两者底层的运行容器并不相同。 WebExceptionHandler Spring-WebFlux 的异常处理器顶层接口,因此追溯到子类可以追踪到 DefaultErrorWebExceptionHandler Spring Cloud Gateway 的全局异常处理器,配置类是 ErrorWebFluxAutoConfiguration

二、为什么要自定义异常处理

先画一个假想但是贴近实际架构图,定位一下网关的作用:

网关在整个架构中的作用是:

  • 路由服务端应用的请求到后端应用。
  • (聚合)后端应用的响应转发到服务端应用。
  • 假设网关服务总是正常的前提下:

    对于第1点来说,假设后端应用不能平滑无损上线,会有一定的几率出现网关路由请求到一些后端的“僵尸节点(请求路由过去的时候,应用更好在重启或者刚好停止)”,这个时候会路由会失败抛出异常,一般情况是Connection Refuse。

    对于第2点来说,假设后端应用没有正确处理异常,那么应该会把异常信息经过网关转发回到服务端应用,这种情况理论上不会出现异常。

    其实还有第3点隐藏的问题,网关如果不单单承担路由的功能,还包含了鉴权、限流等功能,如果这些功能开发的时候对异常捕获没有做完善的处理甚至是逻辑本身存在BUG,有可能导致异常没有被正常捕获处理,走了默认的异常处理器 DefaultErrorWebExceptionHandler ,默认的异常处理器的处理逻辑可能并不符合我们预期的结果。

    三、如何自定义异常处理

    3.1、SpringCloudGateway异常处理类间关系

    org.springframework.boot.autoconfigure.web.reactive.error 包下有三个类用于处理异常。

    上面两个是处理异常的一些逻辑,下面的那个类是异常的配置类,所以我们只需要继承 DefaultErrorWebExceptionHandler 然后将我们处理异常的逻辑替换原有的逻辑。然后通过配置类,将自己写的类替换原有的类即可。

    3.2、自定义异常处理实现

    目前最新的SpringBoot版本是2.3.8,SpringCloud版本是Hoxton.SR10
    在springboot2.3之前是重写下面这个方法:
    Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace)
    在2.3版本之后,
    Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) 已经被标记为过时,使用
    Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) 取代,所以我们需要重写这个方法就可以了。

    3.2.1、(springboot2.3之前版本)

    我们可以先看默认的异常处理器的配置类 ErrorWebFluxAutoConfiguration

    @Configuration
    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
    @ConditionalOnClass(WebFluxConfigurer.class)
    @AutoConfigureBefore(WebFluxAutoConfiguration.class)
    @EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class })
    public class ErrorWebFluxAutoConfiguration {
    	private final ServerProperties serverProperties;
    	private final ApplicationContext applicationContext;
    	private final ResourceProperties resourceProperties;
    	private final List<ViewResolver> viewResolvers;
    	private final ServerCodecConfigurer serverCodecConfigurer;
    	public ErrorWebFluxAutoConfiguration(ServerProperties serverProperties,
    			ResourceProperties resourceProperties,
    			ObjectProvider<ViewResolver> viewResolversProvider,
    			ServerCodecConfigurer serverCodecConfigurer,
    			ApplicationContext applicationContext) {
    		this.serverProperties = serverProperties;
    		this.applicationContext = applicationContext;
    		this.resourceProperties = resourceProperties;
    		this.viewResolvers = viewResolversProvider.orderedStream()
    				.collect(Collectors.toList());
    		this.serverCodecConfigurer = serverCodecConfigurer;
    	@Bean
    	@ConditionalOnMissingBean(value = ErrorWebExceptionHandler.class,
    			search = SearchStrategy.CURRENT)
    	@Order(-1)
    	public ErrorWebExceptionHandler errorWebExceptionHandler(
    			ErrorAttributes errorAttributes) {
    		DefaultErrorWebExceptionHandler exceptionHandler = new DefaultErrorWebExceptionHandler(
    				errorAttributes, this.resourceProperties,
    				this.serverProperties.getError(), this.applicationContext);
    		exceptionHandler.setViewResolvers(this.viewResolvers);
    		exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
    		exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
    		return exceptionHandler;
    	@Bean
    	@ConditionalOnMissingBean(value = ErrorAttributes.class,
    			search = SearchStrategy.CURRENT)
    	public DefaultErrorAttributes errorAttributes() {
    		return new DefaultErrorAttributes(
    				this.serverProperties.getError().isIncludeException());
    

    注意到两个Bean实例ErrorWebExceptionHandlerDefaultErrorAttributes都使用了@ConditionalOnMissingBean注解,也就是我们可以通过自定义实现去覆盖它们。先自定义一个CustomErrorWebFluxAutoConfiguration(除了ErrorWebExceptionHandler的自定义实现,其他直接拷贝ErrorWebFluxAutoConfiguration):

    @Configuration
    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
    @ConditionalOnClass(WebFluxConfigurer.class)
    @AutoConfigureBefore(WebFluxAutoConfiguration.class)
    @EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
    public class CustomErrorWebFluxAutoConfiguration {
        private final ServerProperties serverProperties;
        private final ApplicationContext applicationContext;
        private final ResourceProperties resourceProperties;
        private final List<ViewResolver> viewResolvers;
        private final ServerCodecConfigurer serverCodecConfigurer;
        public CustomErrorWebFluxAutoConfiguration(ServerProperties serverProperties,
                                                   ResourceProperties resourceProperties,
                                                   ObjectProvider<ViewResolver> viewResolversProvider,
                                                   ServerCodecConfigurer serverCodecConfigurer,
                                                   ApplicationContext applicationContext) {
            this.serverProperties = serverProperties;
            this.applicationContext = applicationContext;
            this.resourceProperties = resourceProperties;
            this.viewResolvers = viewResolversProvider.orderedStream()
                    .collect(Collectors.toList());
            this.serverCodecConfigurer = serverCodecConfigurer;
        @Bean
        @ConditionalOnMissingBean(value = ErrorWebExceptionHandler.class,
                search = SearchStrategy.CURRENT)
        @Order(-1)
        public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
            // TODO 这里完成自定义ErrorWebExceptionHandler实现逻辑
            return null;
        @Bean
        @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
        public DefaultErrorAttributes errorAttributes() {
            return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
    

    ErrorWebExceptionHandler的实现,可以直接参考DefaultErrorWebExceptionHandler,甚至直接继承DefaultErrorWebExceptionHandler,覆盖对应的方法即可。这里直接把异常信息封装成下面格式的Response返回,最后需要渲染成JSON格式:

    "code": 200, "message": "描述信息", "path" : "请求路径", "method": "请求方法"

    我们需要分析一下DefaultErrorWebExceptionHandler中的一些源码:

    // 封装异常属性
    protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
    	return this.errorAttributes.getErrorAttributes(request, includeStackTrace);
    // 渲染异常Response
    protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
    	boolean includeStackTrace = isIncludeStackTrace(request, MediaType.ALL);
    	Map<String, Object> error = getErrorAttributes(request, includeStackTrace);
    	return ServerResponse.status(getHttpStatus(error))
    			.contentType(MediaType.APPLICATION_JSON_UTF8)
    			.body(BodyInserters.fromObject(error));
    // 返回路由方法基于ServerResponse的对象
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
    	return route(acceptsTextHtml(), this::renderErrorView).andRoute(all(), this::renderErrorResponse);
    // HTTP响应状态码的封装,原来是基于异常属性的status属性进行解析的
    protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
    	int statusCode = (int) errorAttributes.get("status");
    	return HttpStatus.valueOf(statusCode);
    

    确定三点:

  • 最后封装到响应体的对象来源于DefaultErrorWebExceptionHandler#getErrorAttributes(),并且结果是一个Map<String, Object>实例转换成的字节序列。
  • 原来的RouterFunction实现只支持HTML格式返回,我们需要修改为JSON格式返回(或者说支持所有格式返回)。
  • DefaultErrorWebExceptionHandler#getHttpStatus()是响应状态码的封装,原来的逻辑是基于异常属性getErrorAttributes()的status属性进行解析的。
  • 自定义的JsonErrorWebExceptionHandler如下:

    public class JsonErrorWebExceptionHandler extends