Spring Boot 中关于自定义异常处理的套路!

在 Spring Boot 项目中 ,异常统一处理,可以使用 Spring 中 @ControllerAdvice 来统一处理,也可以自己来定义异常处理方案。Spring Boot 中,对异常的处理有一些默认的策略,我们分别来看。

默认情况下,Spring Boot 中的异常页面 是这样的:

我们从这个异常提示中,也能看出来,之所以用户看到这个页面,是因为开发者没有明确提供一个 /error 路径,如果开发者提供了 /error 路径 ,这个页面就不会展示出来,不过在 Spring Boot 中,提供 /error 路径实际上是下下策,Spring Boot 本身在处理异常时,也是当所有条件都不满足时,才会去找 /error 路径。那么我们就先来看看,在 Spring Boot 中,如何自定义 error 页面,整体上来说,可以分为两种,一种是静态页面,另一种是动态页面。

静态异常页面

自定义静态异常页面,又分为两种,第一种 是使用 HTTP 响应码来命名页面,例如 404.html、405.html、500.html ….,另一种就是直接定义一个 4xx.html,表示400-499 的状态都显示这个异常页面,5xx.html 表示 500-599 的状态显示这个异常页面。

默认是在 classpath:/static/error/ 路径下定义相关页面:

此时,启动项目,如果项目抛出 500 请求错误,就会自动展示 500.html 这个页面,发生 404 就会展示 404.html 页面。如果异常展示页面既存在 5xx.html,也存在 500.html ,此时,发生500异常时,优先展示 500.html 页面。

动态异常页面

动态的异常页面定义方式和静态的基本 一致,可以采用的页面模板有 jsp、freemarker、thymeleaf。动态异常页面,也支持 404.html 或者 4xx.html ,但是一般来说,由于动态异常页面可以直接展示异常详细信息,所以就没有必要挨个枚举错误了 ,直接定义 4xx.html(这里使用thymeleaf模板)或者 5xx.html 即可。

注意,动态页面模板,不需要开发者自己去定义控制器,直接定义异常页面即可 ,Spring Boot 中自带的异常处理器会自动查找到异常页面。

页面定义如下:

页面内容如下:

1234567891011121314151617181920212223242526272829303132<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head><meta charset="UTF-8"><title>Title</title></head><body><h1>5xx</h1><table border="1"><tr><td>path</td><td th:text="${path}"></td></tr><tr><td>error</td><td th:text="${error}"></td></tr><tr><td>message</td><td th:text="${message}"></td></tr><tr><td>timestamp</td><td th:text="${timestamp}"></td></tr><tr><td>status</td><td th:text="${status}"></td></tr></table></body></html>

默认情况下,完整的异常信息就是这5条,展示 效果如下 :

如果动态页面和静态页面同时定义了异常处理页面,例如 classpath:/static/error/404.htmlclasspath:/templates/error/404.html 同时存在时,默认使用动态页面。即完整的错误页面查找方式应该是这样:

发生了500错误–>查找动态 500.html 页面–>查找静态 500.html –> 查找动态 5xx.html–>查找静态 5xx.html。

自定义异常数据

默认情况下,在Spring Boot 中,所有的异常数据其实就是上文所展示出来的5条数据,这5条数据定义在 org.springframework.boot.web.reactive.error.DefaultErrorAttributes 类中,具体定义在 getErrorAttributes 方法中 :

1234567891011121314@Overridepublic Map<String, Object> getErrorAttributes(ServerRequest request,boolean includeStackTrace) {Map<String, Object> errorAttributes = new LinkedHashMap<>();errorAttributes.put("timestamp"new Date());errorAttributes.put("path", request.path());Throwable error = getError(request);HttpStatus errorStatus = determineHttpStatus(error);errorAttributes.put("status", errorStatus.value());errorAttributes.put("error", errorStatus.getReasonPhrase());errorAttributes.put("message", determineMessage(error));handleException(errorAttributes, determineException(error), includeStackTrace);return errorAttributes;}

DefaultErrorAttributes 类本身则是在org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration 异常自动配置类中定义的,如果开发者没有自己提供一个 ErrorAttributes 的实例的话,那么 Spring Boot 将自动提供一个ErrorAttributes 的实例,也就是 DefaultErrorAttributes 。

基于此 ,开发者自定义 ErrorAttributes 有两种方式 :

  1. 直接实现 ErrorAttributes 接口
  2. 继承 DefaultErrorAttributes(推荐),因为 DefaultErrorAttributes 中对异常数据的处理已经完成,开发者可以直接使用。

具体定义如下:

1234567891011@Componentpublic class MyErrorAttributes  extends DefaultErrorAttributes {@Overridepublic Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);if ((Integer)map.get("status") == 500) {map.put("message""服务器内部错误!");}return map;}}

定义好的 ErrorAttributes 一定要注册成一个 Bean ,这样,Spring Boot 就不会使用默认的 DefaultErrorAttributes 了,运行效果如下图:

自定义异常视图

异常视图默认就是前面所说的静态或者动态页面,这个也是可以自定义的,首先 ,默认的异常视图加载逻辑在 org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController 类的 errorHtml 方法中,这个方法用来返回异常页面+数据,还有另外一个 error 方法,这个方法用来返回异常数据(如果是 ajax 请求,则该方法会被触发)。

12345678910@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)public ModelAndView errorHtml(HttpServletRequest request,HttpServletResponse response) {HttpStatus status = getStatus(request);Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));response.setStatus(status.value());ModelAndView modelAndView = resolveErrorView(request, response, status, model);return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);}

在该方法中 ,首先会通过 getErrorAttributes 方法去获取异常数据(实际上会调用到 ErrorAttributes 的实例 的 getErrorAttributes 方法),然后调用 resolveErrorView 去创建一个 ModelAndView ,如果这里创建失败,那么用户将会看到默认的错误提示页面。

正常情况下, resolveErrorView 方法会来到 DefaultErrorViewResolver 类的 resolveErrorView 方法中:

123456789@Overridepublic ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,Map<String, Object> model) {ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);}return modelAndView;}

在这里,首先以异常响应码作为视图名分别去查找动态页面和静态页面,如果没有查找到,则再以 4xx 或者 5xx 作为视图名再去分别查找动态或者静态页面。

要自定义异常视图解析,也很容易 ,由于 DefaultErrorViewResolver 是在 ErrorMvcAutoConfiguration 类中提供的实例,即开发者没有提供相关实例时,会使用默认的 DefaultErrorViewResolver ,开发者提供了自己的 ErrorViewResolver 实例后,默认的配置就会失效,因此,自定义异常视图,只需要提供 一个 ErrorViewResolver 的实例即可:

12345678910@Componentpublic class MyErrorViewResolver extends DefaultErrorViewResolver {public MyErrorViewResolver(ApplicationContext applicationContext, ResourceProperties resourceProperties) {super(applicationContext, resourceProperties);}@Overridepublic ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {return new ModelAndView("/aaa/123", model);}}

实际上,开发者也可以在这里定义异常数据(直接在 resolveErrorView 方法重新定义一个 model ,将参数中的model 数据拷贝过去并修改,注意参数中的 model 类型为 UnmodifiableMap,即不可以直接修改),而不需要自定义MyErrorAttributes。定义完成后,提供一个名为123的视图,如下图:

如此之后,错误试图就算定义成功了。

总结

实际上也可以自定义异常控制器 BasicErrorController ,不过松哥觉得这样太大动干戈了,没必要,前面几种方式已经可以满足我们的大部分开发需求了。

Image placeholder
beyond923
未设置
  69人点赞

没有讨论,发表一下自己的看法吧

推荐文章
SpringBoot个人应用开发框架(SpringBoot版本2.1)+IDEA

前言: 此笔记为本人首个SpringBoot项目框架学习实践记录,期间参考了许多大神的笔记和心得。 参考文档如下: 项目git地址: 一、创建SpringBoot工程 1.1创建父POM工程结

再见 Spring Boot 1.X ,Spring Boot 2.X 走向舞台中心

2019年8月6日,Spring官方在其博客宣布,SpringBoot1.x停止维护,SpringBoot1.x生命周期正式结束。其实早在2018年7月30号,Spring官方就已经在博客进行过预告,

Spring-SpringAOP原理,手写Spring事务框架

一、Spring核心知识Spring是一个开源框架,Spring是于2003年兴起的一个轻量级的Java开发框架,由RodJohnson在其著作ExpertOne-On-OneJ2EEDevelopm

SpringBoot 整合 Dubbo

1.整合dubbo 有的人或许会说已经有spring-cloud了,你整合dubbo干什么,其实没啥意图,主要就是想整合一下,毕竟dubbo在国内使用的还是很多的,你会一点点总不至于让你显得那么尴尬。

Python 异常处理笔记

什么是异常? 异常简单理解,就是非正常,没有达到预期目标。异常是一个事件,并且这个异常事件在我们程序员的运行过程中出现,会影响我们程序正常执行。 异常分两种: 1.语法错误导致的异常 2.逻辑错误

Python 异常处理笔记

什么是异常? 异常简单理解,就是非正常,没有达到预期目标。异常是一个事件,并且这个异常事件在我们程序员的运行过程中出现,会影响我们程序正常执行。 异常分两种: 1.语法错误导致的异常 2.逻辑错误

SpringBoot 集成 JWT 实现 token 验证,token 注销

什么是JWT Jsonwebtoken(JWT),是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC7519).定义了一种简洁的,自包含的方法用于通信双方之间以JSON对象的形

springboot 多数据源,最简单的整合方式

简介 相信大家有配置过多数据源,或者即将配置多数据的朋友们,会发现网上大概有以下几种方案: 1.使用AOP切片进行动态数据源切换 2.使用MapperScan的basePackages配置不同的map

你知道如何自动保存 Spring Boot 应用进程号吗

1.前言 欢迎阅读SpringBoot2实战系列文章PID对于系统运维来说并不陌生,但是对于一些开发者特别是新手还是要简单介绍一下的。它是ProcessID的简称,是系统分配给一个进程的唯一标识符,

云端的生存之道,第 1 单元:将 Spring Boot 部署到 Kubernetes

初始化Kubernetes集群 第一步是初始化IBMCloud上的Kubernetes集群。IBMCloud可能需要几分钟时间来启动新的Kubernetes集群;因此,通过先执行初始化操作,可以在后台

云端的生存之道,第 2 单元:将 Spring Boot 应用程序连接到云托管的数据库

前提条件 本系列教程的第1部分,因为本教程直接以第1部分中的课程内容和完成的操作为基础。 一个IBMCloud帐户 云原生数据持久性 IBMCloud提供了许多可持久存储数据的选项。在本教程中,我

Spring Boot自动装配整理

首先写一个我们自己的HelloWorld配置类 1、基于"注解驱动"实现@Enable模块 @ConfigurationpublicclassHelloWorldConfiguration{@Bean

SpringBoot2.0 支持 https 访问

买了dapideng.com,自然要上https。 其实在之前的博客中,也早有提及配置证书的事儿,只不过这次变成了springboot,它内置了tomcat容器,和把项目打包放在tomcat下面不太一

SpringBoot 中的 Servlet Web 容器

1.前言 SpringBoot支持一下嵌入式Servlet容器: SpringBoot2.0.3.RELEASE需要Java8或9以及SpringFramework5.0.7.RELEASE或更高版本

Spring Boot 高效数据聚合之道

项目地址和示例代码:https://github.com/lvyahui8/spring-boot-data-aggregator 背景 接口开发是后端开发中最常见的场景,可能是RESTFul接口,也

Spring boot 如何快速的配置多个 Redis 数据源

Redis简介 redis多数据源主要的运用场景是在需要使用多个redis服务器或者使用多个redis库,本文采用的是fastdep依赖集成框架,快速集成Redis多数据源并集成lettuce连接池,

Spring Boot到底是怎么运行的,你知道吗?

导读SpringBoot方式的项目开发已经逐步成为Java应用开发领域的主流框架,它不仅可以方便地创建生产级的Spring应用程序,还能轻松地通过一些注解配置与目前比较流行的微服务框架SpringCl

Spring Boot 面试,一个问题就干趴下了!

随着SpringBoot使用越来越广泛,SpringBoot已经成为Java程序员面试的知识点,很多同学对SpringBoot理解不是那么深刻,经常就会被几个连环跑给干趴下了!比如下面这一段的Spri

SpringBoot 深度调优,让你的项目飞起来!

项目调优作为一名工程师,项目调优这事,是必须得熟练掌握的事情。在SpringBoot项目中,调优主要通过配置文件和配置JVM的参数的方式进行。一、修改配置文件关于修改配置文件application.p

使用 Docker 部署 Spring Boot 项目

Docker技术发展为微服务落地提供了更加便利的环境,使用Docker部署SpringBoot其实非常简单,这篇文章我们就来简单学习下。首先构建一个简单的SpringBoot项目,然后给项目添加Doc

Github 上 Star 最多的 Spring Boot 个人开源学习项目

2016年,在一次技术调研的过程中认识到了SpringBoot,试用之后便一发不可收拾的爱上它。为了防止学习之后忘记,就在网上连载了 SpringBoot系列文章,没想到这一开始便与SpringBoo

Spring Boot 中的响应式编程和 WebFlux 入门

Spring5.0中发布了重量级组件Webflux,拉起了响应式编程的规模使用序幕。WebFlux使用的场景是异步非阻塞的,使用Webflux作为系统解决方案,在大多数场景下可以提高系统吞吐量。Spr

SpringBoot整合RabbitMQ

SpringBoot整合RabbitMQSpringBoot框架已经提供了RabbitMQ的使用jar包,开发人员在使用RabbitMQ的时候只需要引用jar包简单的配置一下就可以使用RabbitMQ