前言
Spring Boot中进行统一的异常处理,包括了两种方式的处理:
- 对 API 接口进行异常处理,统一封装返回格式
- 对模板页面请求的异常处理,统一处理错误页面。
准备工作
(1)pom 文件
在 pom 文件中需要引入的依赖如下所示:
<dependencies>
<dependency>
<!--web依赖-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--thymeleaf依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--测试依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
(2)项目配置
在 application.yaml 配置 文件中主要是对 thymeleaf 进行配置,如下:
spring:
thymeleaf:
cache: false # 关闭缓存
mode: HTML5 # 页面是html5类型
encoding: UTF-8 # 采用utf-8编码
servlet:
content-type: text/html # 文档MIME类型是text/html
(3)错误处理页面
构建一个错误页面处理视图,如下,这里的 message 表示异常信息
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head lang="en">
<meta charset="UTF-8"/>
<title>统一页面异常处理</title>
</head>
<body>
<h1>统一页面异常处理</h1>
<div th:text="${message}"></div>
</body>
</html>
构建项目
(1)定义响应状态枚举
响应状态枚举类,规定了 Api 接口响应的实体类型
package com.project.enums;
/**
* @author 孟亚辉
* @time 2022/10/9 16:03
*/
public enum StatusEnum {
/**
* 操作成功
*/
SUCCESS(200, "操作成功"),
/**
*未知异常
*/
UNKNOWN_ERROR(500, "服务器出错啦");
/**
* 状态码
*/
final int code;
/**
* 内容
*/
final String msg;
StatusEnum(int code, String message) {
this.code = code;
this.msg = message;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
(2)封装 Api 响应实体
package com.project.domain;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.project.enums.StatusEnum;
import com.project.exception.SystemException;
import lombok.Data;
/**
* @author 孟亚辉
* @time 2022/10/9 16:01
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)// 为 null 的字段不序列化
public class ApiResult {
private Integer code;
private String msg;
private Object data;
private ApiResult() {
}
public ApiResult(Integer code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
/**
* 构造一个自定义的响应实体
*
* @param code 状态码
* @param message 返回内容
* @param data 返回数据
* @return ResponseResult
*/
public static ApiResult of(Integer code, String message, Object data) {
return new ApiResult(code, message, data);
}
/**
* 构造一个有状态且带数据的响应实体返回
*
* @param status 状态枚举
* @param data 响应数据
* @return ResponseResult
*/
public static ApiResult ofStatus(StatusEnum status, Object data) {
return of(status.getCode(), status.getMsg(), data);
}
/**
* 构造一个有状态的响应实体返回
*
* @param status 响应状态
* @return ResponseResult
*/
public static ApiResult ofStatus(StatusEnum status) {
return ofStatus(status, null);
}
/**
* 构造一个成功且带数据的响应实体返回
*
* @param data 响应数据
* @return ResponseResult
*/
public static ApiResult ofSuccess(Object data) {
return ofStatus(StatusEnum.SUCCESS, data);
}
/**
* 构造一个成功且自定义消息的响应实体返回
*
* @param message 响应消息
* @return ResponseResult
*/
public static ApiResult ofMessage(String message) {
return of(StatusEnum.SUCCESS.getCode(), message, null);
}
/**
* 构造一个异常且带数据的响应实体返回
*
* @param t 异常
* @param data 响应数据
* @param <T> {@link SystemException} 的子类
* @return ResponseResult
*/
public static <T extends SystemException> ApiResult ofException(T t, Object data) {
return of(t.getCode(), t.getMsg(), data);
}
/**
* 构造一个异常的响应实体返回
*
* @param t 异常
* @param <T> {@link SystemException} 的子类
* @return ResponseResult
*/
public static <T extends SystemException> ApiResult ofException(T t) {
return ofException(t, t.getMsg());
}
}
(3)封装异常
上面在进行封装 Api 响应实体时,使用了 SystemException ,此异常为自定义异常,往下看就会知道它的妙用。
package com.project.exception;
import com.project.enums.StatusEnum;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author 孟亚辉
* @time 2022/10/9 16:18
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class SystemException extends RuntimeException{
private Integer code;
private String msg;
public SystemException(StatusEnum result) {
this.code = result.getCode();
this.msg = result.getMsg();
}
public SystemException(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}
它继承了 RuntimeException 所以,项目在抛出 SystemException 时不会停止,所以我们可以拦截此类型的异常,获取异常信息进行处理,对于如何拦截异常下面会讲到。此外,可以看到 SystemException 的成员变量为响应状态码和响应消息,所以在构造方法中可以使用响应状态枚举类。
对于 Api 异常信息和模板页面异常信息的拦截这里分别自定义异常 JsonException 和 PageException 来处理,这两个异常均继承自 SystemException 。
- JsonException
package com.project.exception;
import com.project.enums.StatusEnum;
/**
* @author 孟亚辉
* @time 2022/10/9 16:55
*/
public class JsonException extends SystemException{
public JsonException(StatusEnum result) {
super(result);
}
public JsonException(Integer code, String message) {
super(code, message);
}
}
- PageException
package com.project.exception;
import com.project.enums.StatusEnum;
/**
* @author 孟亚辉
* @time 2022/10/9 16:56
*/
public class PageException extends SystemException{
public PageException(StatusEnum result) {
super(result);
}
public PageException(Integer code, String message) {
super(code, message);
}
}
(4)定义全局异常处理器
@ControllerAdvice 是@Controller 的增强版,主要用来处理全局数据,一般搭配 @ExceptionHandler、@ModelAttribute 以及 @InitBinder 使用。其中最常见的使用场景就是配合 @ExceptionHandler 实现全局异常处理。
抛出的 JsonException 类型的异常会被 jsonErrorHandler 方法拦截,同样抛出的 PageException 类型的异常会被 pageErrorHandler 方法拦截。
package com.project.handler;
import com.project.domain.ApiResult;
import com.project.exception.JsonException;
import com.project.exception.PageException;
import lombok.extern.slf4j.Slf4j;
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;
/**
* @author 孟亚辉
* @time 2022/10/9 16:53
*/
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
private static final String DEFAULT_ERROR_VIEW = "error";
@ResponseBody
@ExceptionHandler(value = JsonException.class)
public ApiResult jsonErrorHandler(JsonException exception) {
log.error("【JsonException】:{}", exception.getMsg());
return ApiResult.ofException(exception);
}
@ExceptionHandler(value = PageException.class)
public ModelAndView pageErrorHandler(PageException exception){
log.error("【PageException】:{}", exception.getMsg());
ModelAndView view = new ModelAndView();
view.addObject("message", exception.getMsg());
view.setViewName(DEFAULT_ERROR_VIEW);
return view;
}
}
测试
(1)测试代码
测试的代码无非是在 controller 中抛出异常,代码如下:
package com.project.controller;
import com.project.domain.ApiResult;
import com.project.enums.StatusEnum;
import com.project.exception.JsonException;
import com.project.exception.PageException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
/**
* @author 孟亚辉
* @time 2022/10/9 17:00
*/
@Controller
public class TestController {
@GetMapping("/json")
@ResponseBody
public ApiResult jsonException() {
throw new JsonException(StatusEnum.UNKNOWN_ERROR);
}
@GetMapping("/page")
public ModelAndView pageException() {
throw new PageException(StatusEnum.UNKNOWN_ERROR);
}
}
(2)测试结果
启动项目后,在浏览器中访问下面两个地址:
结果如下图所示: