⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 www.cnblogs.com/bndong/p/10135370.html 「BNDong」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

前言

在启动应用时会发现在控制台打印的日志中出现了两个路径为 {[/error]} 的访问地址,当系统中发送异常错误时,Spring Boot 会根据请求方式分别跳转到以 JSON 格式或以界面显示的 /error 地址中显示错误信息。

2018-12-18 09:36:24.627  INFO 19040 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" ...
2018-12-18 09:36:24.632 INFO 19040 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" ...

默认异常处理

使用 AJAX 方式请求时返回的 JSON 格式错误信息。

{
"timestamp": "2018-12-18T01:50:51.196+0000",
"status": 404,
"error": "Not Found",
"message": "No handler found for GET /err404",
"path": "/err404"
}

使用浏览器请求时返回的错误信息界面。

自定义异常处理

引入依赖

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

fastjson 是 JSON 序列化依赖, spring-boot-starter-freemarker 是一个模板引擎,用于我们设置错误输出模板。

增加配置

# 出现错误时, 直接抛出异常(便于异常统一处理,否则捕获不到404)
spring.mvc.throw-exception-if-no-handler-found=true
# 不要为工程中的资源文件建立映射
spring.resources.add-mappings=false
spring:
# 出现错误时, 直接抛出异常(便于异常统一处理,否则捕获不到404)
mvc:
throw-exception-if-no-handler-found: true
# 不要为工程中的资源文件建立映射
resources:
add-mappings: false

新建错误信息实体

/**
* 信息实体
*/
public class ExceptionEntity implements Serializable {

private static final long serialVersionUID = 1L;

private String message;

private int code;

private String error;

private String path;

@JSONField(format = "yyyy-MM-dd hh:mm:ss")
private Date timestamp = new Date();

public static long getSerialVersionUID() {
return serialVersionUID;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

public int getCode() {
return code;
}

public void setCode(int code) {
this.code = code;
}

public String getError() {
return error;
}

public void setError(String error) {
this.error = error;
}

public String getPath() {
return path;
}

public void setPath(String path) {
this.path = path;
}

public Date getTimestamp() {
return timestamp;
}

public void setTimestamp(Date timestamp) {
this.timestamp = timestamp;
}
}

新建自定义异常

/**
* 自定义异常
*/
public class BasicException extends RuntimeException {

private static final long serialVersionUID = 1L;

private int code = 0;

public BasicException(int code, String message) {
super(message);
this.code = code;
}

public int getCode() {
return this.code;
}
}
/**
* 业务异常
*/
public class BusinessException extends BasicException {

private static final long serialVersionUID = 1L;

public BusinessException(int code, String message) {
super(code, message);
}
}

BasicException 继承了 RuntimeException ,并在原有的 Message 基础上增加了错误码 code 的内容。而 BusinessException 则是在业务中具体使用的自定义异常类,起到了对不同的异常信息进行分类的作用。

新建 error.ftl 模板文件

位置:/src/main/resources/templates/ 用于显示错误信息

<!DOCTYPE html>
<html>
<head>
<meta name="robots" content="noindex,nofollow" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<style>
h2{
color: #4288ce;
font-weight: 400;
padding: 6px 0;
margin: 6px 0 0;
font-size: 18px;
border-bottom: 1px solid #eee;
}

/* Exception Variables */
.exception-var table{
width: 100%;
max-width: 500px;
margin: 12px 0;
box-sizing: border-box;
table-layout:fixed;
word-wrap:break-word;
}
.exception-var table caption{
text-align: left;
font-size: 16px;
font-weight: bold;
padding: 6px 0;
}
.exception-var table caption small{
font-weight: 300;
display: inline-block;
margin-left: 10px;
color: #ccc;
}
.exception-var table tbody{
font-size: 13px;
font-family: Consolas,"Liberation Mono",Courier,"微软雅黑";
}
.exception-var table td{
padding: 0 6px;
vertical-align: top;
word-break: break-all;
}
.exception-var table td:first-child{
width: 28%;
font-weight: bold;
white-space: nowrap;
}
.exception-var table td pre{
margin: 0;
}
</style>
</head>
<body>

<div class="exception-var">
<h2>Exception Datas</h2>
<table>
<tbody>
<tr>
<td>Code</td>
<td>
${(exception.code)!}
</td>
</tr>
<tr>
<td>Time</td>
<td>
${(exception.timestamp?datetime)!}
</td>
</tr>
<tr>
<td>Path</td>
<td>
${(exception.path)!}
</td>
</tr>
<tr>
<td>Exception</td>
<td>
${(exception.error)!}
</td>
</tr>
<tr>
<td>Message</td>
<td>
${(exception.message)!}
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>

编写全局异常控制类

/**
* 全局异常控制类
*/
@ControllerAdvice
public class GlobalExceptionHandler {

/**
* 404异常处理
*/
@ExceptionHandler(value = NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ModelAndView errorHandler(HttpServletRequest request, NoHandlerFoundException exception, HttpServletResponse response) {
return commonHandler(request, response,
exception.getClass().getSimpleName(),
HttpStatus.NOT_FOUND.value(),
exception.getMessage());
}

/**
* 405异常处理
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ModelAndView errorHandler(HttpServletRequest request, HttpRequestMethodNotSupportedException exception, HttpServletResponse response) {
return commonHandler(request, response,
exception.getClass().getSimpleName(),
HttpStatus.METHOD_NOT_ALLOWED.value(),
exception.getMessage());
}

/**
* 415异常处理
*/
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public ModelAndView errorHandler(HttpServletRequest request, HttpMediaTypeNotSupportedException exception, HttpServletResponse response) {
return commonHandler(request, response,
exception.getClass().getSimpleName(),
HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(),
exception.getMessage());
}

/**
* 500异常处理
*/
@ExceptionHandler(value = Exception.class)
public ModelAndView errorHandler (HttpServletRequest request, Exception exception, HttpServletResponse response) {
return commonHandler(request, response,
exception.getClass().getSimpleName(),
HttpStatus.INTERNAL_SERVER_ERROR.value(),
exception.getMessage());
}

/**
* 业务异常处理
*/
@ExceptionHandler(value = BasicException.class)
private ModelAndView errorHandler (HttpServletRequest request, BasicException exception, HttpServletResponse response) {
return commonHandler(request, response,
exception.getClass().getSimpleName(),
exception.getCode(),
exception.getMessage());
}

/**
* 表单验证异常处理
*/
@ExceptionHandler(value = BindException.class)
@ResponseBody
public ExceptionEntity validExceptionHandler(BindException exception, HttpServletRequest request, HttpServletResponse response) {
List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
Map<String,String> errors = new HashMap<>();
for (FieldError error:fieldErrors) {
errors.put(error.getField(), error.getDefaultMessage());
}
ExceptionEntity entity = new ExceptionEntity();
entity.setMessage(JSON.toJSONString(errors));
entity.setPath(request.getRequestURI());
entity.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
entity.setError(exception.getClass().getSimpleName());
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
return entity;
}

/**
* 异常处理数据处理
*/
private ModelAndView commonHandler (HttpServletRequest request, HttpServletResponse response,
String error, int httpCode, String message) {
ExceptionEntity entity = new ExceptionEntity();
entity.setPath(request.getRequestURI());
entity.setError(error);
entity.setCode(httpCode);
entity.setMessage(message);
return determineOutput(request, response, entity);
}

/**
* 异常输出处理
*/
private ModelAndView determineOutput(HttpServletRequest request, HttpServletResponse response, ExceptionEntity entity) {
if (!(
request.getHeader("accept").contains("application/json")
|| (request.getHeader("X-Requested-With") != null && request.getHeader("X-Requested-With").contains("XMLHttpRequest"))
)) {
ModelAndView modelAndView = new ModelAndView("error");
modelAndView.addObject("exception", entity);
return modelAndView;
} else {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setCharacterEncoding("UTF8");
response.setHeader("Content-Type", "application/json");
try {
response.getWriter().write(ResultJsonTools.build(
ResponseCodeConstant.SYSTEM_ERROR,
ResponseMessageConstant.APP_EXCEPTION,
JSONObject.parseObject(JSON.toJSONString(entity))
));
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
}

@ControllerAdvice

作用于类上,用于标识该类用于处理全局异常。

@ExceptionHandler

作用于方法上,用于对拦截的异常类型进行处理。value 属性用于指定具体的拦截异常类型,如果有多个 ExceptionHandler 存在,则需要指定不同的 value 类型,由于异常类拥有继承关系,所以 ExceptionHandler 会首先执行在继承树中靠前的异常类型。

BindException

该异常来自于表单验证框架 Hibernate validation,当字段验证未通过时会抛出此异常。

编写测试 Controller

@RestController
public class TestController {

@RequestMapping(value = "err")
public void error(){
throw new BusinessException(400, "业务异常错误信息");
}

@RequestMapping(value = "err2")
public void error2(){
throw new NullPointerException("手动抛出异常信息");
}

@RequestMapping(value = "err3")
public int error3(){
int a = 10 / 0;
return a;
}
}

使用 AJAX 方式请求时返回的 JSON 格式错误信息。

# /err
{
"msg": "应用程序异常",
"code": -1,
"status_code": 0,
"data": {
"path": "/err",
"code": 400,
"error": "BusinessException",
"message": "业务异常错误信息",
"timestamp": "2018-12-18 11:09:00"
}
}

# /err2
{
"msg": "应用程序异常",
"code": -1,
"status_code": 0,
"data": {
"path": "/err2",
"code": 500,
"error": "NullPointerException",
"message": "手动抛出异常信息",
"timestamp": "2018-12-18 11:15:15"
}
}

# /err3
{
"msg": "应用程序异常",
"code": -1,
"status_code": 0,
"data": {
"path": "/err3",
"code": 500,
"error": "ArithmeticException",
"message": "/ by zero",
"timestamp": "2018-12-18 11:15:46"
}
}

# /err404
{
"msg": "应用程序异常",
"code": -1,
"status_code": 0,
"data": {
"path": "/err404",
"code": 404,
"error": "NoHandlerFoundException",
"message": "No handler found for GET /err404",
"timestamp": "2018-12-18 11:16:11"
}
}

使用浏览器请求时返回的错误信息界面。

示例代码:https://github.com/BNDong/spring-cloud-examples/tree/master/spring-cloud-zuul/cloud-zuul

文章目录
  1. 1. 前言
  2. 2. 默认异常处理
  3. 3. 自定义异常处理
    1. 3.0.1. 引入依赖
    2. 3.0.2. 增加配置
    3. 3.0.3. 新建错误信息实体
    4. 3.0.4. 新建自定义异常
    5. 3.0.5. 新建 error.ftl 模板文件
    6. 3.0.6. 编写全局异常控制类
    7. 3.0.7. 编写测试 Controller