Author:Gorit
date:2020/6/26
SpringBoot 版本: 2.3.1
开发工具:idea 2019 3
一、全局异常处理介绍
在项目开发的过程中,总是会有各种我们意想不到的异常发生,和数据库操作,业务层操作,控制层操作。都有可能遇到异常需要处理,如果每个异常都要需要我们单独处理,那么我们岂不是遇到一个问题,就要自己手动处理一个异常,这样到最后我们的程序之间的耦合还是增大了不少,这也是我们不希望看到的。所有就有了 全局异常处理 的概念。
使用全局异常处理 就能够拦截我们项目中出现得异常,这样也避免了大量的 try catch,也实现了 异常信息的统一处理和维护,同时,我们不希望把异常抛给给用户,应该对异常的错误信息进行封装,然后返回一个友好的信息给用户。
二、全局异常处理之定义统一的 JSON 结构
在 3.3.2 中,我们讲过如何返回一个 JSON 格式的数据,大家不会的可以回到上一节去看。
下面就是我定义好的一个全局异常处理的类
package cn.gorit.common.lang;/*** 封装统一结果集返回数据* */public class Result {private Object data;private String msg;private int code;public Object getData() {return data;}public void setData(Object data) {this.data = data;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}public int getCode() {return code;}public void setCode(int code) {this.code = code;}// 操作成功返回数据public static Result succ(Object data) {return succ(200, "操作成功", data);}public static Result succ(int code, String msg, Object data) {Result r = new Result();r.setCode(code);r.setMsg(msg);r.setData(data);return r;}public static Result succ(String msg, Object data) {return succ(200,msg,data);}// 操作异常返回public static Result fail(int code, String msg, Object data) {Result r = new Result();r.setCode(code);r.setMsg(msg);r.setData(data);return r;}public static Result fail(String msg) {return fail(400,msg,null);}public static Result fail(int code, String msg) {return fail(code,msg,"null");}public static Result fail(String msg, Object data) {return fail(400,msg,data);}}
三、编写全局异常处理类 (GlobalExceptionHandler)
在这里要讲一下一个用到的注解:@RestControllerAdvice
写过 Controller 的我们知道, RestController 注解会将我们返回的内容转换成 JSON 格式返回回去。
接下来我们看看这个注解包含了哪些内容
我们发现这个注解包含了 @ControllerAdvice 和 @ResponseBody
然后接着看 @ControllerAdvice 包含了哪些
我们看到了 Compoent 注解,因此这个注解是被 Spring 所管理的。接着看它的属性,有个叫做 basePackages 的属性,在网上查阅资料得知,这个属性是用来来接收来自哪个包的异常信息,一般我们不指定这个属性,我们拦截整个工程当中的所有异常信息。
具体怎么使用呢?
在方法上添加 @ExceptionHandler 注解来指定具体的异常,然后在方法中处理该异常信息,然后通过统一结果集,返回 JSON 数据给调用者。
四、全局异常处理使用示例
4.1 处理参数缺失异常
前后端分离的项目架构当中,前端请求后台的接口是通过 rest 风格调用的,我们一般发起网络请求,使用 get 或者 post 通常都会携带一些参数。但是这些参数往往会出现漏掉的情况,此时我们需要定义一个参数缺失异常的方法,来给前端调用者返回一个友好信息。
参数缺失会抛出 HttpMessageNotReadableException 异常,我们可以拦截这个异常,并做一个友好的处理返回给前端。
我们接下来定义一个全局异常处理的类 GlobalExceptionHandler
package cn.gorit.common.exception;import cn.gorit.common.lang.Result;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.http.HttpStatus;import org.springframework.web.bind.MissingServletRequestParameterException;import org.springframework.web.bind.annotation.*;/*** ControllerAdvice 注解的功能,能拦截所有的异常* */@RestControllerAdvicepublic class GlobalExceptionHandler {// 打印 log//记得加上这个哇,这是告诉全世界,你要开始在这类里面使用日志protected static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);// 参数缺失异常处理@ExceptionHandler(MissingServletRequestParameterException.class)@ResponseStatus(value = HttpStatus.BAD_REQUEST)public Result handleHttpMessageNotReadableException (MissingServletRequestParameterException ex) {logger.error("缺少请求参数, {}",ex.getMessage());return Result.fail("缺少必要的请求参数");}}
编写一个测试用的 Controller,用于接收前端的请求。
package cn.gorit.controller;import cn.gorit.common.exception.GlobalExceptionHandler;import cn.gorit.common.lang.Result;import cn.gorit.pojo.entity.User;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class TestController {protected static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);@RequestMapping("/")public String index() {return "Hello World";}@PostMapping("/test")public Result test(@RequestParam("name") String name, @RequestParam("pass") String pass) {// 日志打印logger.info("name {}",name);logger.info("pass {}",pass);User user = new User(name,pass);return Result.succ("操作成功",user);}}
启动项目,使用 Postman 测试项目的运行结果,发送一个 post 请求,并且携带参数,正常返回结果集
控制台的日志
接下来看看异常,少一个参数的情况
控制台也打印出了对应的错误
4.2 处理空指针异常
空指针异常就很常见了,我们创建 bean ,就经常会引用一些以为创建,实际上并没有的 bean,这样就会导致出现控制真异常,这个问题其实用的多了就司空见惯了。
接下来我们就模拟一个空指针异常,首先编写全局异常处理的拦截方法
GlobalExceptionHandler 的处理方法// 空指针异常@ExceptionHandler(NullPointerException.class)@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)public Result handleTypeMismatchException(NullPointerException ex) {logger.error("空指针异常,{}",ex.getMessage());return Result.fail(500,"空指针异常",null);}
在控制层中,我们模拟一个 空指针异常
@RequestMapping("/test1")
public void handleNullPointerException() {
User u = null;
u.getUsername();
}
然后测试
4.3 处理所有异常
异常会有很多种,还有编译期异常、运行期异常。这个方法就是一劳永逸的方法,因为 Exception 是所有异常的父类,所有的异常都会继承该异常,所以直接拦截 Exception 异常,就可以做到一劳永逸了。
编写异常处理一劳永逸的方法
// 一劳永逸
@ExceptionHandler(Exception.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public Result handleUnexpectedServer(Exception e) {
logger.error("系统异常");
return Result.fail(500,"系统内部出现异常");
}
使用这一个方法,就要把前面的异常处理的方法注释掉,这样所有的异常都会变成统一的一个样子
但是在正式的开发中,不可能只有一种异常,这样的话我们也不好对项目出问题的地方进行定位。所以一般都会把全局异常处理放在最后一个地方
五、自定义错误页面
在新建的 SpringBoot 项目中的 resource/static/ 目录新建一个 404.html 文件
