文章目录
在Spring Boot中进行统一的异常处理,包括两种方式的处理:第一种对常见API形式的接口进行异常处理,统一封装返回格式;第二种是对模板页面请求的异常处理,统一处理错误页面
基于GitHub项目xkcoding/**spring-boot-demo**进行学习
项目地址:https://github.com/xkcoding/spring-boot-demo
项目结构
exception/handler
包下一共有五个包:constant
包:保存状态码及对应信息controller
包:控制器exception
包:放置异常基类及其子类handler
包:放置统一异常处理(json和页面异常处理)model
包
pom.xml
- 页面需要使用thymeleaf模板引擎渲染,所以需要导入thymeleaf依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-boot-demo-exception-handler</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-boot-demo-exception-handler</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>com.xkcoding</groupId>
<artifactId>spring-boot-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--模板引擎thymeleaf依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<finalName>spring-boot-demo-exception-handler</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件application.yml
server:
port: 8080
servlet:
context-path: /demo
spring:
thymeleaf:
cache: false # 禁用thymeleaf模板缓存
mode: HTML
encoding: UTF-8
servlet:
content-type: text/html
页面模板error.html
<!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>
状态码封装类Status
- 这种通用类一般会放在common包下,在这里放在了constant包下
package com.xkcoding.exception.handler.constant;
import lombok.Getter;
/**
* <p>
* 状态码封装
* </p>
*
* @package: com.xkcoding.exception.handler.constant
* @description: 状态码封装
* @author: yangkai.shen
* @date: Created in 2018/10/2 9:02 PM
* @copyright: Copyright (c) 2018
* @version: V1.0
* @modified: yangkai.shen
*/
@Getter
public enum Status {
/**
* 操作成功
*/
OK(200, "操作成功"),
/**
* 未知异常
*/
UNKNOWN_ERROR(500, "服务器出错啦");
/**
* 状态码
*/
private Integer code;
/**
* 内容
*/
private String message;
Status(Integer code, String message) {
this.code = code;
this.message = message;
}
}
异常类
异常基类BaseException
- 异常基类需要继承运行时异常类
RuntimeException
package com.xkcoding.exception.handler.exception;
import com.xkcoding.exception.handler.constant.Status;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* <p>
* 异常基类
* </p>
*
* @package: com.xkcoding.exception.handler.exception
* @description: 异常基类
* @author: yangkai.shen
* @date: Created in 2018/10/2 9:31 PM
* @copyright: Copyright (c) 2018
* @version: V1.0
* @modified: yangkai.shen
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class BaseException extends RuntimeException {
/**
* 状态码和消息字段
*/
private Integer code;
private String message;
public BaseException(Status status) {
// 输出异常信息
super(status.getMessage());
this.code = status.getCode();
this.message = status.getMessage();
}
public BaseException(Integer code, String message) {
super(message);
this.code = code;
this.message = message;
}
}
JSON异常类JsonException
package com.xkcoding.exception.handler.exception;
import com.xkcoding.exception.handler.constant.Status;
import lombok.Getter;
/**
* <p>
* JSON异常
* </p>
*
* @package: com.xkcoding.exception.handler.exception
* @description: JSON异常
* @author: yangkai.shen
* @date: Created in 2018/10/2 9:18 PM
* @copyright: Copyright (c) 2018
* @version: V1.0
* @modified: yangkai.shen
*/
@Getter
public class JsonException extends BaseException {
public JsonException(Status status) {
super(status);
}
public JsonException(Integer code, String message) {
super(code, message);
}
}
页面处理异常类PageException
package com.xkcoding.exception.handler.exception;
import com.xkcoding.exception.handler.constant.Status;
import lombok.Getter;
/**
* <p>
* 页面异常
* </p>
*
* @package: com.xkcoding.exception.handler.exception
* @description: 页面异常
* @author: yangkai.shen
* @date: Created in 2018/10/2 9:18 PM
* @copyright: Copyright (c) 2018
* @version: V1.0
* @modified: yangkai.shen
*/
@Getter
public class PageException extends BaseException {
public PageException(Status status) {
super(status);
}
public PageException(Integer code, String message) {
super(code, message);
}
}
通用API接口封装类ApiResponse
- 通过这个类可以将各种异常、有状态的API或者自定义消息的API以统一的ApiResponse类型格式返回
package com.xkcoding.exception.handler.model;
import com.xkcoding.exception.handler.constant.Status;
import com.xkcoding.exception.handler.exception.BaseException;
import lombok.Data;
/**
* <p>
* 通用的 API 接口封装
* </p>
*
* @package: com.xkcoding.exception.handler.model
* @description: 通用的 API 接口封装
* @author: yangkai.shen
* @date: Created in 2018/10/2 8:57 PM
* @copyright: Copyright (c) 2018
* @version: V1.0
* @modified: yangkai.shen
*/
@Data
public class ApiResponse {
/**
* 状态码
*/
private Integer code;
/**
* 返回内容
*/
private String message;
/**
* 返回数据
*/
private Object data;
/**
* 无参构造函数
*/
private ApiResponse() {
}
/**
* 全参构造函数
*
* @param code 状态码
* @param message 返回内容
* @param data 返回数据
*/
private ApiResponse(Integer code, String message, Object data) {
this.code = code;
this.message = message;
this.data = data;
}
/**
* 构造一个自定义的API返回
*
* @param code 状态码
* @param message 返回内容
* @param data 返回数据
* @return ApiResponse
*/
public static ApiResponse of(Integer code, String message, Object data) {
return new ApiResponse(code, message, data);
}
/**
* 构造一个成功且带数据的API返回
*
* @param data 返回数据
* @return ApiResponse
*/
public static ApiResponse ofSuccess(Object data) {
return ofStatus(Status.OK, data);
}
/**
* 构造一个成功且自定义消息的API返回
*
* @param message 返回内容
* @return ApiResponse
*/
public static ApiResponse ofMessage(String message) {
return of(Status.OK.getCode(), message, null);
}
/**
* 构造一个有状态的API返回
*
* @param status 状态 {@link Status}
* @return ApiResponse
*/
public static ApiResponse ofStatus(Status status) {
return ofStatus(status, null);
}
/**
* 构造一个有状态且带数据的API返回
*
* @param status 状态 {@link Status}
* @param data 返回数据
* @return ApiResponse
*/
public static ApiResponse ofStatus(Status status, Object data) {
return of(status.getCode(), status.getMessage(), data);
}
/**
* 构造一个异常且带数据的API返回
*
* @param t 异常
* @param data 返回数据
* @param <T> {@link BaseException} 的子类
* @return ApiResponse
*/
public static <T extends BaseException> ApiResponse ofException(T t, Object data) {
// 输出的类型T是BaseException的继承类
// 返回的ApiResponse类
return of(t.getCode(), t.getMessage(), data);
}
/**
* 构造一个异常且带数据的API返回
*
* @param t 异常
* @param <T> {@link BaseException} 的子类
* @return ApiResponse
*/
public static <T extends BaseException> ApiResponse ofException(T t) {
return ofException(t, null);
}
}
统一异常处理类DemoExceptionHandler
- 该类将json异常返回为一个JSON字符串;
- 将页面异常创建一个视图对象,并设置其相关属性,将视图返回到前端,由模板引擎进行解析并显示
package com.xkcoding.exception.handler.handler;
import com.xkcoding.exception.handler.exception.JsonException;
import com.xkcoding.exception.handler.exception.PageException;
import com.xkcoding.exception.handler.model.ApiResponse;
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;
/**
* <p>
* 统一异常处理
* </p>
*
* @package: com.xkcoding.exception.handler.handler
* @description: 统一异常处理
* @author: yangkai.shen
* @date: Created in 2018/10/2 9:26 PM
* @copyright: Copyright (c) 2018
* @version: V1.0
* @modified: yangkai.shen
*/
@ControllerAdvice
@Slf4j
public class DemoExceptionHandler {
private static final String DEFAULT_ERROR_VIEW = "error";
/**
* 统一 json 异常处理
*
* @param exception JsonException
* @return 统一返回 json 格式
*/
@ExceptionHandler(value = JsonException.class)
@ResponseBody
public ApiResponse jsonErrorHandler(JsonException exception) {
// 使用占位符,记录日志信息
log.error("【JsonException】:{}", exception.getMessage());
// 返回一个ApiResponse,本质上是一个JSON字符串,只有code和message,而data为null
return ApiResponse.ofException(exception);
}
/**
* 统一 页面 异常处理
*
* @param exception PageException
* @return 统一跳转到异常页面
*/
@ExceptionHandler(value = PageException.class)
public ModelAndView pageErrorHandler(PageException exception) {
// 记录日志信息
log.error("【DemoPageException】:{}", exception.getMessage());
// 创建一个ModelAndView对象
ModelAndView view = new ModelAndView();
view.addObject("message", exception.getMessage());
// 设置视图的名字为error
// 视图解析器会到templates文件夹下寻找同名的模板,并进行渲染(渲染message字段,将原始数据进行替换)
view.setViewName(DEFAULT_ERROR_VIEW);
return view;
}
}
测试控制器TestController
package com.xkcoding.exception.handler.controller;
import com.xkcoding.exception.handler.constant.Status;
import com.xkcoding.exception.handler.exception.JsonException;
import com.xkcoding.exception.handler.exception.PageException;
import com.xkcoding.exception.handler.model.ApiResponse;
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;
/**
* <p>
* 测试Controller
* </p>
*
* @package: com.xkcoding.exception.handler.controller
* @description: 测试Controller
* @author: yangkai.shen
* @date: Created in 2018/10/2 8:49 PM
* @copyright: Copyright (c) 2018
* @version: V1.0
* @modified: yangkai.shen
*/
@Controller
public class TestController {
@GetMapping("/json")
@ResponseBody
public ApiResponse jsonException() {
throw new JsonException(Status.UNKNOWN_ERROR);
}
@GetMapping("/page")
public ModelAndView pageException() {
throw new PageException(Status.UNKNOWN_ERROR);
}
}
测试
-
访问
http://localhost:8080/demo/json
,可以看到json类型的异常字符串 -
访问
http://localhost:8080/demo/page
,可以看到thymeleaf已经将相关字段渲染为异常信息