1 基本思路
1.1 使用到的异常种类

Java的错误异常分为两个体系:Error、Exception,均继承自Throwable->Object
Error错误类体系:Java虚拟机无法解决的严重问题。如JVM系统内部错误、资源耗尽等严重情况
Exception异常类体系:其它因编程错误或偶然的外在因素导致的一般性问题。又分为编译时异常(checked)和运行时异常(unchecked)。
1.2 使用到的技术点
- 类:使用@ControllerAdvice+@ExceptionHandler处理全局抛出的异常
- 方法:上面定义的全局异常处理类,方法上标注不同的注解。可实现不同的异常进入到不同的处理模块。
- 方法返回值:根据不同的情况判断是返回的页面还是Json对象。
2 具体实现
2.1 通览:自定义全局异常类
package com.efly.gulimall.coupon.helper.exception;import com.efly.gulimall.coupon.helper.base.AjaxResult;import lombok.extern.slf4j.Slf4j;import org.springframework.validation.BindException;import org.springframework.validation.BindingResult;import org.springframework.web.HttpRequestMethodNotSupportedException;import org.springframework.web.bind.MethodArgumentNotValidException;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.RestControllerAdvice;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.util.HashMap;import java.util.Map;/*** 面向切面的异常:自定义异常处理器** @author zhuyf*/@RestControllerAdvice@Slf4jpublic class DefaultExceptionHandler {/*** 自定义的业务异常(可在代码处手动抛出throw new BusinessPageException)** @param request 请求对象,可不传* @param response 响应对象,可不传* @param ex 异常类(这个要和你当前捕获的异常类是同一个)*/@ExceptionHandler(BusinessPageException.class) //也可以只对一个类进行捕获public Object errorHandler(BusinessPageException ex, HttpServletRequest request, HttpServletResponse response) {/** 这里你拿到了request和response对象,你可以做任何你想做的事* 比如:* 1.用request从头信息中拿到Accept来判断是请求方可接收的类型从而进行第一个方法的判断* 2.如果你也想返回一个页面,使用response对象进行重定向到自己的错误页面就可以了*///异常写入数据库日志框架log.error(ex.getMessage(), ex);//异常协商:如果接受类型为html,就返回错误页面,否则返回json文本if (request.getHeader("accept").indexOf("text/html") != -1) {ModelAndView modelAndView = new ModelAndView();modelAndView.setViewName("/error/5xx.html"); // 指定错误跳转页面 需要在templates里面新建 一个error.htmlmodelAndView.addObject("messages", ex.getMessage());modelAndView.addObject("code", ex.getCode());modelAndView.addObject("url", request.getRequestURL());return modelAndView;}return AjaxResult.error("服务器错误,请联系管理员" + ex.getMessage());}/*** Throwable->Exception->HttpRequestMethodNotSupportedException* 捕获请求方式不支持的异常*/@ExceptionHandler({HttpRequestMethodNotSupportedException.class})public AjaxResult handleException(HttpRequestMethodNotSupportedException e) {log.error(e.getMessage(), e);return AjaxResult.error("不支持' " + e.getMethod() + "'请求");}@ExceptionHandler(value = {MethodArgumentNotValidException.class, BindException.class})public AjaxResult handleVaildException(BindException e) {log.error("数据校验出现问题{},异常类型:{}", e.getMessage(), e.getClass());BindingResult bindingResult = e.getBindingResult();Map<String, String> errorMap = new HashMap<>();bindingResult.getFieldErrors().forEach((fieldError) -> {errorMap.put(fieldError.getField(), fieldError.getDefaultMessage());});return AjaxResult.error("数据校验出现问题", errorMap);}/*** Throwable->Exception->RuntimeException* 捕获未知的运行时RuntimeException异常*/@ExceptionHandler(RuntimeException.class)public AjaxResult notFount(RuntimeException e) {//内容可决定是返回AjaxResult还是回到404log.error("运行时异常:", e);return AjaxResult.error("运行时异常:" + e.getMessage());}/*** Throwable->Exception* 捕获Exception异常*/@ExceptionHandler(Exception.class)public AjaxResult handleException(Exception e) {log.error(e.getMessage(), e);return AjaxResult.error("服务器错误,请联系管理员", e.getMessage());}/*** Throwable:最大的异常捕获类* 捕获异常体系最大的Throwable异常*/@ExceptionHandler(value = Throwable.class)public AjaxResult handleException(Throwable throwable) {log.error("错误:", throwable);return AjaxResult.error("服务器错误,请联系管理员" + throwable.getMessage());}}
2.2 拆解:自定义异常类及捕获自定义异常类的处理方法
上面的BussinessPageException是自定义的异常类,该方法识别请求的accept判断是网页发起的请求还是json对象发起的,如果是网页发起的则到模板引擎下的/error/5xx.html,如果是非text/html则返回json对象。
/*** 自定义业务异常** @param request 请求对象,可不传* @param response 响应对象,可不传* @param ex 异常类(这个要和你当前捕获的异常类是同一个)*/@ExceptionHandler(BusinessPageException.class)public Object errorHandler(BusinessPageException ex,HttpServletRequest request,HttpServletResponse response) {/** 这里你拿到了request和response对象,你可以做任何你想做的事* 比如:* 1.用request从头信息中拿到Accept来判断是请求方可接收的类型从而进行第一个方法的判断* 2.如果你也想返回一个页面,使用response对象进行重定向到自己的错误页面就可以了*///异常写入数据库日志框架log.error(ex.getMessage(), ex);//异常协商:如果接受类型为html,就返回错误页面,否则返回json文本if (request.getHeader("accept").indexOf("text/html") != -1) {ModelAndView modelAndView = new ModelAndView();// 指定错误跳转页面 需要在templates里面新建 一个error.htmlmodelAndView.setViewName("/error/5xx.html");modelAndView.addObject("messages", ex.getMessage());modelAndView.addObject("code", ex.getCode());modelAndView.addObject("url", request.getRequestURL());return modelAndView;}return AjaxResult.error("服务器错误,请联系管理员" + ex.getMessage());}
自定义异常类
/*** 自定义异常类** @author zhuyf*/public class BusinessPageException extends RuntimeException {private static final long serialVersionUID = 1L;protected final String message;protected final Integer code;public BusinessPageException(String message, Integer code) {this.message = message;this.code = code;}@Overridepublic String getMessage() {return message;}public Integer getCode() {return code;}}
自定义返回的类型AjaxResult
package com.efly.gulimall.coupon.helper.base;import java.util.HashMap;/*** 操作消息提醒** @author zhuyf*/public class AjaxResult extends HashMap<String, Object> {private static final long serialVersionUID = 1L;/*** 初始化一个新创建的 Message 对象*/public AjaxResult() {}/*** 返回错误消息** @return 错误消息*/public static AjaxResult error() {return error(1, "操作失败", false, "");}/*** 返回错误消息** @param msg 内容* @return 错误消息*/public static AjaxResult error(String msg) {return error(500, msg, false, "");}public static AjaxResult error(String msg, Object data) {return error(500, msg, false, data);}/*** 返回错误消息** @param code 错误码* @param msg 内容* @return 错误消息*/public static AjaxResult error(int code, String msg, boolean isSuccess, Object data) {AjaxResult json = new AjaxResult();json.put("code", code);json.put("msg", msg);json.put("success", isSuccess);json.put("data", data);return json;}/*** 返回成功消息** @param msg 内容* @return 成功消息*/public static AjaxResult success(String msg) {AjaxResult json = new AjaxResult();json.put("msg", msg);json.put("code", 200);json.put("success", true);json.put("data", "");return json;}/*** 返回成功消息** @return 成功消息*/public static AjaxResult success() {return AjaxResult.success("操作成功");}/*** 返回成功消息** @param key 键值* @param value 内容* @return 成功消息*/@Overridepublic AjaxResult put(String key, Object value) {super.put(key, value);return this;}}
2.3 拆解:thymeleaf引擎渲染的error/5xx.html页面
此处使用的是Thymeleaf。注意在实际生产环境里面,具体的错误信息不要显示在错误页面上,在测试环境下可以显示以提高排错效率。
pom加载thymeleaf启动器
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>
ThymeleafView默认路径为:classpath:/templates,加页面放入到templates/下

上面分发的代码
ModelAndView modelAndView = new ModelAndView();// 指定错误跳转页面 需要在templates里面新建 一个error.htmlmodelAndView.setViewName("/error/5xx.html");
- 5xx.html的具体错误内容
```
<!DOCTYPE html>
错误信息:错误状态码:失败API地址:
<a name="xRKOU"></a>## 2.4 拆解:SpringBoot默认的异常处理静态错误页面除了像上面可以使用服务器端渲染的页面(可以动态捕获到具体的错误日志、错误代码、错误路径等信息),SpringBoot也提供了默认的异常信息静态页面的处理功能。<br />对于机器客户端,它将生成并返回JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。<br /><br />对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据。<br /><br />**具体的规则即为**:SpringMVC在执行过程中会捕获异常,然后去找异常解析器去解析异常,如果可以解析就拿到解析器返回的ModelAndView去指定的视图/JSON数据(就是我们上面定义的Thymeleaf引擎下的error/5xx、4xx.html),如果不能就由底层tomcat发送一个error错误,这个error错误会去SpringBoot默认的静态目录下的error/404或error/500页面,404或500是错误代码。```java扩展:springboot将从服务器此目录下读取静态资源数据,默认值为classpath:/META-INF/resources/classpath:/resources/,classpath:/static/,classpath:/public/其中classpath为resources目录
所以我们可以将默认的5xx.html和4xx.html写入到如上默认规则目录里面
具体的页面内容
error/5xx.html
<!DOCTYPE html><html lang="zh"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>CGWANG - 500</title><link href="/css/bootstrap.min.css" rel="stylesheet"/><link href="/css/animate.css" rel="stylesheet"/><link href="/css/style.css" rel="stylesheet"/></head><body class="gray-bg"><div class="middle-box text-center animated fadeInDown"><h1>500</h1><h3 class="font-bold">内部服务器错误!</h3><div class="error-desc">服务器遇到意外事件,不允许完成请求。我们抱歉。您可以返回主页面。<a href="javascript:top.document.location.href='/'" class="btn btn-primary m-t">主页</a></div></div><script src="/js/jquery.min.js?v=2.1.4"></script><script src="/js/bootstrap.min.js?v=3.3.6"></script></body></html>
error/4xx.html页面
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>404</title></head><body>404</body></html>
2.6 测试
有如下的代码
@RequestMapping("/list")public R list(@RequestParam Map<String, Object> params) {//手动抛出异常throw new BusinessPageException("进入了错误页面", 500);}
在网页端访问

如果使用PostMan由于没有像网页端一样传递了Accept:text/html,则返回的是Jason对象
