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 注解的功能,能拦截所有的异常
* */
@RestControllerAdvice
public 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;
@RestController
public 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 文件