在Spring Boot中进行统一的异常处理,包括两种方式的处理:第一种对常见API形式的接口进行异常处理,统一封装返回格式;第二种是对模板页面请求的异常处理,统一处理错误页面
基于GitHub项目xkcoding/**spring-boot-demo**进行学习
项目地址:https://github.com/xkcoding/spring-boot-demo
exception/handler
包下一共有五个包:
constant
包:保存状态码及对应信息controller
包:控制器exception
包:放置异常基类及其子类handler
包:放置统一异常处理(json和页面异常处理)model
包
<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.0modelVersion>
<artifactId>spring-boot-demo-exception-handlerartifactId>
<version>1.0.0-SNAPSHOTversion>
<packaging>jarpackaging>
<name>spring-boot-demo-exception-handlername>
<description>Demo project for Spring Bootdescription>
<parent>
<groupId>com.xkcodinggroupId>
<artifactId>spring-boot-demoartifactId>
<version>1.0.0-SNAPSHOTversion>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
dependencies>
<build>
<finalName>spring-boot-demo-exception-handlerfinalName>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
server:
port: 8080
servlet:
context-path: /demo
spring:
thymeleaf:
cache: false # 禁用thymeleaf模板缓存
mode: HTML
encoding: UTF-8
servlet:
content-type: text/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>
package com.xkcoding.exception.handler.constant;
import lombok.Getter;
/**
*
* 状态码封装
*
*
* @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;
}
}
RuntimeException
package com.xkcoding.exception.handler.exception;
import com.xkcoding.exception.handler.constant.Status;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
*
* 异常基类
*
*
* @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;
}
}
package com.xkcoding.exception.handler.exception;
import com.xkcoding.exception.handler.constant.Status;
import lombok.Getter;
/**
*
* JSON异常
*
*
* @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);
}
}
package com.xkcoding.exception.handler.exception;
import com.xkcoding.exception.handler.constant.Status;
import lombok.Getter;
/**
*
* 页面异常
*
*
* @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);
}
}
package com.xkcoding.exception.handler.model;
import com.xkcoding.exception.handler.constant.Status;
import com.xkcoding.exception.handler.exception.BaseException;
import lombok.Data;
/**
*
* 通用的 API 接口封装
*
*
* @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 {@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 {@link BaseException} 的子类
* @return ApiResponse
*/
public static <T extends BaseException> ApiResponse ofException(T t) {
return ofException(t, null);
}
}
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;
/**
*
* 统一异常处理
*
*
* @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;
}
}
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;
/**
*
* 测试Controller
*
*
* @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已经将相关字段渲染为异常信息