0
点赞
收藏
分享

微信扫一扫

Day06_09_SpringBoot教程之统一异常处理实现方案及原理解析


SpringBoot统一异常处理方案及原理解析

一. SpringBoot默认异常处理简介

我们在做Web应用的时候,处理请求的过程中发生错误是非常常见的情况,那么在发生了错误的时候,SpringBoot是如何处理的呢?

1. 错误处理概述

Spring Boot 默认提供了一个程序出错的结果映射路径 ​​/error​​​.这个 ​​/error​​​ 请求会在 ​​BasicErrorController​​​ 中处理,其内部是通过判断请求头中的​​Accept​​​的内容是否为​​text/html​​​来区分请求是来自客户端浏览器(浏览器通常默认自动发送请求头内容​​Accept:text/html​​),还是客户端接口的调用,以此来决定是返回一个页面视图还是返回一个 JSON 消息内容.

2. /error映射默认处理策略

所以在默认情况下,Spring Boot为这两种情况提供了不同的响应方式.

2.1 响应'Whitelabel Error Page'页面

一种是浏览器客户端请求一个不存在的页面或服务端处理发生异常时,这时候一般Spring Boot默认会响应一个html文档内容,称作“Whitelabel Error Page”:


Day06_09_SpringBoot教程之统一异常处理实现方案及原理解析_异常处理

2.2 响应JSON字符串

另一种是当我们使用Postman等调试工具发送请求一个不存在的url或服务端处理发生异常时,Spring Boot会返回类似如下的一个 JSON 字符串信息:

{
"timestamp": "2019-05-20T06:12:45.209+0000",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/login.html"
}

3. SpringBoot的错误信息配置

在上面的2个章节中,我们讲述了SpringBoot的错误处理策略,接下来分析源码中的错误配置信息是如何实现的.

SpringBoot的错误配置信息是通过​​ErrorMvcAutoConfiguration​​来进行配置的,这个类中帮我们注册了以下组件:

DefaultErrorAttributes: 帮我们在页面上共享错误信息;
BasicErrorController: 处理默认的 /error 请求,分为两种处理请求方式:一种是html方式,一种是json方式;
ErrorPageCustomizer: 系统发生错误后,该对象就会生效,来定义请求规则;
DefaultErrorViewResolver: 默认的错误视图解析器,将错误信息解析到相应的错误视图.

4. Springboot处理error请求的原理流程

一旦系统中出现 4xx 或者 5xx 之类的错误, ​​ErrorPageCustomizer​​​就会生效(定义错误的相应规则),就会发起 ​​/error​​​ 请求,接着就会被​​BasicErrorController​​​处理.然后​​BasicErrorController​​​会根据请求头​​RequestHeaders​​​中的​​Accept​​​来区分该请求是浏览器发来的请求还是由其它客户端工具发来的请求.分为两种处理方法:一个是​​errorHtml()​​​和​​error()​​​.在​​errorHtml()​​​方法中,获取错误状态信息,由​​resolveErrorView​​​解析器解析到默认的错误视图页面,错误页面是​​/error/404.html​​​页面.而如果​​templates​​​目录中的​​error​​​目录里面有这个页面,404错误就会精确匹配​​404.html​​​,如果没有这个​​404.html​​​页面它会模糊匹配​​4xx.html​​​页面,如果​​templates​​​中没有找到错误页面,它就会去​​static​​文件中找.

简单概括就是:

  • 1️⃣. 当出现4xx或5xx错误:​​ErrorPageCustomizer​​开始生效;
  • 2️⃣. 然后​​ErrorPageCustomizer​​发起 ​​/error​​ 请求;
  • 3️⃣. ​​/error​​ 请求被​​BasicErrorController​​处理;
  • 4️⃣. ​​BasicErrorController​​根据请求头中的​​Accept​​决定如何响应处理.

5. 在error页面中可以获取的错误信息

timestamp: 时间戳.
status: 状态码.
error: 错误提示.
exception: 异常对象.
message: 异常消息.
errors: 数据效验相关的信息.

注意:

static文件夹存放的是静态页面,它没有办法使用模板引擎表达式.

了解了上面的计息过程以后,我们自定义错误就简单了,自定义html错误页面分为两种情况:

有模板引擎的情况下,在templates文件夹下建立一个error文件夹,里面以错误号的方式添加错误页面.我们也可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的其它错误,精确优先.

没有模板引擎的情况下,在静态资源文件夹下添加以错误号命名的错误页面.

二. BasicErrorController源码解析

在SpringBoot中​​BasicErrorController​​​ 这个类是用来捕获 ​​/error​​​ 的所有错误的,当过滤器中发生错误会被重定向到 ​​/error​​.


Day06_09_SpringBoot教程之统一异常处理实现方案及原理解析_spring_02

注释信息中说明了,这是一个Spring Boot自带的全局错误Controller.

这个Controller中有个RequestMapping地址,这相当于一个三元运算符的写法,如果你在配置文件配置了​​server.error.path​​​的话,就会使用你配置的异常处理地址,如果没有就会使用你配置的​​error.path​​​路径地址,如果还是没有,则默认使用​​/error​​来作为发生异常的处理地址,如下图:


Day06_09_SpringBoot教程之统一异常处理实现方案及原理解析_html_03


在​​BasicErrorController​​​类中提供了两个接口处理方法,上面一个标注了​​produces​​​为​​text/html​​​,也就是当你是网页请求的时候返回网页数据;而在下面的接口方法中,则表示当你的请求为其他格式的时候,返回的是​​ResponseEntity​​对象(会转换成json数据或其他格式,取决与你的返回数据类型配置).我们看到第一个方法返回了一个error页面,如果你的项目静态页面下刚好存在一个error所对应的页面,那么Spring Boot会得到你本地的页面,如下图:

Day06_09_SpringBoot教程之统一异常处理实现方案及原理解析_spring_04

那么这个BasicErrorController默认返回的信息是哪一些呢,如下图:


Day06_09_SpringBoot教程之统一异常处理实现方案及原理解析_spring_05


这个方法是属于类DefaultErrorAttributes,返回一个map,这个map里面包含了异常发生时间,异常状态,异常详细信息,发生异常的请求路径.如下图是一个异常发生的时候所获取到的异常信息:


Day06_09_SpringBoot教程之统一异常处理实现方案及原理解析_html_06


那么,我们该如何抛弃掉或者覆盖掉Spring Boot默认异常处理呢,Spring Boot开发指南上提供了以下四种方法:

1️⃣. 自定义一个bean,实现ErrorController接口,那么默认的错误处理机制将不再生效;

2️⃣. 自定义一个bean,继承BasicErrorController类,使用一部分现成的功能,自己也可以添加新的public方法,使用@RequestMapping及其produces属性指定新的地址映射;

3️⃣. 自定义一个ErrorAttribute类型的bean,那么还是默认的两种响应方式,只不过改变了内容项而已;

4️⃣. 继承AbstractErrorController.

三. SpringBoot中统一异常处理实现--响应一个html页面

在日常的 Web 开发中如果发生了异常,往往需要通过一个统一的异常处理,来保证客户端能够收到友好的提示.接下来介绍 Spring Boot 中的全局统一异常处理实现方案.

1. 创建一个新的web模块---boot_exception_handler


Day06_09_SpringBoot教程之统一异常处理实现方案及原理解析_html_07

2. 在pom.xml中添加相关依赖

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<!--为了解决thymeleaf模板中,对html标签要求太严格的问题!-->
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
<version>1.9.22</version>
</dependency>
</dependencies>

3. application.properties配置文件

# Enable template caching.
spring.thymeleaf.cache=true

# Check that the templates location exists.
spring.thymeleaf.check-template-location=true

# Content-Type value.
spring.thymeleaf.content-type=text/html

# Enable MVC Thymeleaf view resolution.
spring.thymeleaf.enabled=true

# Template encoding.
spring.thymeleaf.encoding=UTF-8

# Template mode to be applied to templates. See also StandardTemplateModeHandlers.
spring.thymeleaf.mode=LEGACYHTML5

# Prefix that gets prepended to view names when building a URL.
spring.thymeleaf.prefix=classpath:/templates/

# Suffix that gets appended to view names when building a URL.
spring.thymeleaf.suffix=.html

4. 创建一个Controller,抛出异常测试

package com.syc.boot.web;

import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

@RequestMapping("/")
public String index(ModelMap map) throws Exception {
//验证spring-boot全局异常捕获,人为抛出一个异常
throw new Exception("随便抛个异常测测...");
}
}

5. 创建统一异常处理类

通过使用​​@ControllerAdvice​​​注解定义统一的异常处理类,而不是在每个Controller中逐个定义;​​@ExceptionHandler​​注解用来定义函数针对的异常类型,最后将Exception对象和请求URL映射到error.html中.

package com.syc.boot.handler;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import org.thymeleaf.spring4.expression.Mvc;

import javax.servlet.http.HttpServletRequest;

/**
*要捕获全局异常只需要以下几步即可:
* 1. 创建一个类,在类上面添加@ControllerAdvice注解;
* 2.任意创建一个方法,参数是HttpServletRequest和Exception,
* 在该方法上面添加@ExceptionHandler注解,方法返回值如果是字符串,
* 则还需要添加@ResponseBody,如果返回的是页面,则返回 ModelAndView 对象;
* 3.根据自己的业务逻辑决定返回某种类型的内容.
*/
@ControllerAdvice
public class GlobalExceptionHandler {

private static final String DEFAULT_ERROR_VIEW = "error";

@ExceptionHandler(value = Exception.class)
public ModelAndView exceptionHandler(HttpServletRequest request, Exception e) {
System.out.println("统一异常处理:" + e.getMessage());

ModelAndView mv = new ModelAndView();
mv.addObject("exception", e);
mv.addObject("url", request.getRequestURL());
mv.setViewName(DEFAULT_ERROR_VIEW);
return mv;
}
}

6. 创建一个自定义的error.html处理页面

实现error.html页面展示:在templates目录下创建error.html,将请求的URL和Exception对象的message输出。

<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8" />
<title>SpringBoot统一异常处理</title>
</head>
<body>
<h1>Error Handler</h1>
<div th:text="${url}"></div>
<div th:text="${exception.message}"></div>
</body>
</html>

7. 项目结构


Day06_09_SpringBoot教程之统一异常处理实现方案及原理解析_html_08

8. 统一异常处理测试

启动该应用,访问:​​http://localhost:8080/hello,可以看到如下错误提示页面​​:


Day06_09_SpringBoot教程之统一异常处理实现方案及原理解析_html_09

以后我们只要在Controller中抛出了Exception,只要定义了一个​​@ControllerAdvice​​​类,SpringBoot就会根据抛出的具体Exception类型来匹配​​@ExceptionHandler​​中配置的异常类型,从而找到一个错误映射和处理方法.

四. SpringBoot中统一异常处理实现--返回JSON格式

在上述例子中,通过​​@ControllerAdvice​​​注解统一定义不同的Exception,从而映射到不同的错误处理页面.而当我们要实现RESTful API时,返回的错误是JSON格式的数据,而不是HTML页面,这时我们只需在​​@ExceptionHandler​​​之后加入​​@ResponseBody​​,就能将处理函数return的内容转换为JSON格式.

下面具体讲述如何实现返回JSON格式的异常处理方案.

1. 创建统一的JSON返回对象

创建统一的JSON返回对象,code:消息类型;message:消息内容;url:请求的url;data:请求返回的数据.

package com.syc.boot.domain;

import lombok.Data;

/**
*
*/
@Data
public class ErrorInfo<T> {

public static final Integer OK = 0;
public static final Integer ERROR = 100;

private Integer code;
private String message;
private String url;
private T data;

}

2. 创建一个自定义异常类CustomException

该类用来实验捕获该异常,并返回json.

package com.syc.boot.handler;

/**
* 自定义异常
*/
public class CustomException extends Exception {

public CustomException(String message) {
super(message);
}
}

3. 在controller中创建一个接口方法

@RequestMapping("/json")
public String handleJsonException() throws CustomException {
throw new CustomException("json格式的异常处理方案...");
}

4. 在GlobalExceptionHandler类中创建一个异常处理方法

@ExceptionHandler(value = CustomException.class)
@ResponseBody
public ErrorInfo<String> jsonErrorHandler(HttpServletRequest req, CustomException e) throws Exception {
ErrorInfo<String> info = new ErrorInfo<>();
info.setMessage(e.getMessage());
info.setCode(ErrorInfo.ERROR);
info.setData("JSON格式异常处理信息");
info.setUrl(req.getRequestURL().toString());
return info;
}

5. 项目结构

Day06_09_SpringBoot教程之统一异常处理实现方案及原理解析_spring_10

6. 验证结果

启动应用,访问:​​http://localhost:8080/json,得到如下响应内容​​:


Day06_09_SpringBoot教程之统一异常处理实现方案及原理解析_html_11

至此,已完成在Spring Boot中创建统一的异常处理,实际实现还是依靠Spring MVC的注解,更多更深入的使用可参考Spring MVC的文档.

举报

相关推荐

0 条评论