logo头像

👨‍💻冷锋のIT小屋

Spring boot全局异常处理和自定义异常页面

Spring boot提供了默认的异常处理机制,但是难以满足业务需要,一般需要编码来实现自己的业务处理机制。在本篇,将介绍如何自定义异常页面,和进行全局的异常处理。

1. 全局异常处理

如果系统业务处理发生异常,我们希望能够将异常信息以友好的形式返回给调用方,而不是仅在后台记录日志,尤其是在开发RESTFul的API时,需要将业务异常信息进行明确定义并返回给API调用方,这显得尤为重要。

现在,我们来定义一个全局的业务异常类BusinessException,如果业务处理失败抛出该类或者其子类,然后编写一个全局异常处理器,将异常转换为有好的信息并返回。

1、定义异常类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class BusinessException extends Exception {
public BusinessException() {
super();
}

public BusinessException(String msg) {
super(msg);
}

public BusinessException(Throwable cause) {
super(cause);
}

public BusinessException(String message, Throwable cause) {
super(message, cause);
}

public BusinessException(MsgDefinition msgDefinition) {
super(msgDefinition.msgOf());
this.msgDef = msgDefinition;
}

public MsgDefinition msgDef() {
return msgDef == null ? MsgDefinition.UNKOWN_ERROR : msgDef;
}
}

其中的MsgDefinition为具体的错误信息,包含错误码code和错误提示信息msg,子类继承该类进行扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class MsgDefinition {
public static final MsgDefinition SUCCESS = new MsgDefinition("0000", "请求成功");

public static final MsgDefinition EMPTY_ARGUMENTS = new MsgDefinition("4001", "请求参数为空");
public static final MsgDefinition ILLEGAL_ARGUMENTS = new MsgDefinition("4002", "请求采参数非法");
public static final MsgDefinition FILE_SIZE_OVER_LIMIT = new MsgDefinition("4301", "文件大小超过限制");
public static final MsgDefinition FILE_NUMBER_OVER_LIMIT = new MsgDefinition("4302", "文件数量超过限制");
public static final MsgDefinition FILE_FORMAT_UNSUPPORTED = new MsgDefinition("4310", "文件格式不支持");

public static final MsgDefinition UNKOWN_ERROR = new MsgDefinition("9999", "系统未知异常");

private String code;
private String msg;

public MsgDefinition(String code, String msg) {
this.code = code;
this.msg = msg;
}

public String codeOf() {
return this.code;
}

public String msgOf() {
return this.msg;
}

@Override
public String toString() {
return JsonUtil.toJson(this);
}
}

2、定义全局异常处理器

我们希望在每个Controller的每个@RequestMapping方法抛出异常时进行处理,需要使用@ControllerAdvice注解,另外,还需要使用@ExceptionHandler,它用于声明处理的异常类型,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = {BusinessException.class})
@ResponseBody
public ResultMsg jsonErrorHandler(HttpServletRequest request, BusinessException exception)
{
URLResultMsg result = new URLResultMsg();
String uri = request.getRequestURI();
result.setUrl(uri);
MsgDefinition md = exception.msgDef();
if (md != null) {
String code = md.codeOf();
String msg = md.msgOf();
result.setRtnCode(code);
result.setRtnMsg(msg);
result.setType(ResultMsg.MESSAGE_TYPE_ERROR);
} else {
result.setRtnCode(MsgDefinition.UNKOWN_ERROR.codeOf());
result.setRtnMsg(exception.getMessage());
result.setType(ResultMsg.MESSAGE_TYPE_ERROR);
}
return result;
}
}

jsonErrorHandle为异常处理方法,  当Controller抛出BusinessException时,会进入该方法。其逻辑很简单,通过获取BusinessException的消息定义对象,将异常转换为URLResultMsg,并返回,最终该对象会被转换为json字符串。

3、测试

编写一个Controller

1
2
3
4
5
6
7
8
9
@Controller
public class HomeController {

@GetMapping("/json")
@ResponseBody
public ResultMsg json() throws Exception {
throw new BusinessException(MsgDefinition.EMPTY_ARGUMENTS);
}
}

直接让其抛出BusinessException异常,启动应用,访问http://localhost:8080/json,得到结果为:

``{"rtnCode":"4001","rtnMsg":"请求参数为空","data":null,"type":"error","url":"/json"}``

说明全局异常处理器生效,成功将异常转换为更友好的错误提示信息。业务上,只需要继承BusinessException异常,来定义自身更明确的异常,而不需要修改全局异常处理器。

2. 自定义异常页面

Spring Boot本身提供的错误页面非常不友好,需要自己定义,而方法有多种。

1a59a3293ef041d696d182f6d5317872

最简单有效的方式是,在工程中直接新建错误页面,Spring Boot会优先使用之。一般而言,应用会根据Http请求的错误码来决定使用哪个页面,比如常见的500、404、400、405等,Spring Boot提供了重写这些页面的方式,只需在工程的文件夹中存放新建的这些页面即可,比如404页面存放路径如下:

使用静态html:

1
2
3
4
5
6
7
8
9
src/
+- main/
+- java/
| +
+- resources/
+- public/
+- error/
| +- 404.html
+-

使用模板:

1
2
3
4
5
6
7
8
9
src/
+- main/
+- java/
| +
+- resources/
+- templates/
+- error/
| +- 5xx.ftl
+-

注意页面名称必须与HTTP错误码相同,并且是放在error文件夹下。也可以使用模糊匹配,如上边的5xx.ftl,所有的5xx错误码都会被映射到该页面中。

另外,Spring Boot会将页面Model放入如下信息:

  • timestamp:错误时间戳

  • status:Http状态码

  • error:Http错误信息

  • message:后台定义的错误信息

  • path:请求的uri

我们看一个5xx页面的例子:

1、定义5xx错误页面

这里使用的是默认的Thymeleaf模板,页面位置为src/main/resources/templates/error/5xx.html,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"></meta>
<title>5xx</title>
</head>
<body>
<h2>对不起,系统出错了!</h2>
<p th:text="'timestamp : ' + ${timestamp}"></p>
<p th:text="'status : ' + ${status}"></p>
<p th:text="'error : ' + ${error}"></p>
<p th:text="'message : ' + ${message}"></p>
<p th:text="'path : ' + ${path}"></p>
<p></p>
</body>
</html>

2、测试

修改上边的HomeController,添加方法:

1
2
3
4
5
@GetMapping("/err")
@ResponseBody
public ResultMsg error() throws Exception {
throw new Exception("错误信息");
}

启动应用,访问http://localhost:8080/err,可以看到已经成功显示5xx.html,并且输出了正确的信息。

Spring Boot还提供了更加精确的控制自定义页面的方式,这里不再详述。

示例源码见 Github

支付宝打赏 微信打赏

赞赏是不耍流氓的鼓励