我们在做Web应用的时候,请求处理过程中发生错误是非常常见的情况。Spring Boot提供了一个默认的映射:/error,当处理中抛出异常之后,会转到该请求中处理,并且该请求有一个全局的错误页面用来展示异常内容。
默认情况下,Spring Boot为两种情况提供了不同的响应方式
1、当浏览器发送请求头是Accept: text/html;Spring Boot默认会响应一个html文档内容,称作“Whitelabel Error Page”。
2、当使用postman等调试工具发送请求一个不存在的url或服务端处理发生异常时,Spring Boot会返回类似如下的Json格式字符串信息。
{
"timestamp": "2019-05-31T06:11:45.209+0000",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/index.html"
}
Spring Boot 默认提供了程序出错的结果映射路径/error。这个/error请求会在BasicErrorController中处理,其内部是通过判断请求头中的Accept的内容是否为text/html来区分请求是来自客户端浏览器(浏览器通常默认自动发送请求头内容Accept:text/html)还是客户端接口的调用,以此来决定返回页面视图还是 JSON 消息内容。处理源码如下:
全局统一异常处理
Springboot中定义了默认的error错误映射,但是在实际项目使用中,这些默认的错误映射对我们来说并不实用,所以我们需要实现符合实际业务场景的异常提示。本文我们来学习springboot针对web应用的统一异常处理。
- 局部异常处理 @Controller + @ExceptionHandler
- 全局异常处理 @ControllerAdvice + @ExceptionHandler
1、局部异常处理 @Controller + @ExceptionHandler
局部异常主要用到的是@ExceptionHandler注解,此注解注解到类的方法上,当此注解里定义的异常抛出时,此方法会被执行。如果@ExceptionHandler所在的类是@Controller,则此方法只作用在此类。如果@ExceptionHandler所在的类带有@ControllerAdvice注解,则此方法会作用在全局。
2、全局异常处理 @ControllerAdvice + @ExceptionHandler
SpringBoot中有一个ControllerAdvice的注解,使用该注解表示开启了全局异常的捕获,我们只需在自定义一个方法使用ExceptionHandler注解然后定义捕获异常的类型即可对这些捕获的异常进行统一的处理。
注意:基于@ControllerAdvice注解的全局异常统一处理只能针对于Controller层的异常,简单的说,进入Controller层的错误才会由@ControllerAdvice处理,拦截器抛出的错误以及访问错误地址的情况@ControllerAdvice处理不了,由SpringBoot默认的异常处理机制处理。
使用@ControllerAdvice或者@RestControllerAdvice定义统一的全局异常处理类,而不是在controller中逐个定义。使用@ExceptionHandler来定义函数针对的异常类型。
@RestControllerAdvice 类似于 @RestController 与 @Controller的区别
全局统一异常示例
上面说了springboot异常原理,在实际开发中,如果是要实现RESTful API,那么默认的JSON错误信息就不是我们想要的(本质上,只需在@ExceptionHandler之后加入@ResponseBody,就能让处理函数return的内容转换为JSON格式。),这时候就需要统一一下JSON格式,所以常常自己要封装一下。封装后的数据返回如下:
正确的时候
{
code:200,
msg:“获取列表成功”,
data:{
queryList :[]
}
}
错误的时候
{
code:500,
msg:“未知异常,请联系管理员”
}
import java.io.Serializable;
/**
* 自定义标准响应格式
* @author: liumengbing
* @date: 2019/05/31 16:35
**/
public class ResponseModel implements Serializable {
private Integer status;//响应状态码
private String message;//响应信息
private Object data;//响应内容
public ResponseModel() {
}
public ResponseModel(Integer status, String message) {
this.status = status;
this.message = message;
}
public ResponseModel(Integer status, String message, Object data) {
this.status = status;
this.message = message;
this.data = data;
}
//getter and setter …
}
/**
* 自定义异常,这里要继承runtimeException是因为spring框架对运行时异常会进行回滚,如果是exception就不会了
* @author: liumengbing
* @date: 2019/05/31 17:03
**/
public class MyException extends RuntimeException {
private int code;
private Object data;
public MyException() {
}
public MyException(int code, String message) {
super(message);
this.code = code;
}
public MyException(int code, String message, Object data) {
super(message);
this.code = code;
this.data = data;
}
public int getCode() {
return code;
}
public Object getData() {
return data;
}
@Override
public String toString() {
return "MyException{" +
"code=" + code +
", data=" + data +
", message=" + getMessage() +
'}';
}
}
/**
* 全局异常处理类
* @author: liumengbing
* @date: 2019/05/31 16:36
**/
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 当发生Exception异常时,执行此方法
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseModel exHandler(Exception e) {
// 判断发生异常的类型是除0异常则做出响应
if (e instanceof ArithmeticException) {
return new ResponseModel(500,"发生了除0异常");
}
// 未知的异常做出响应
return new ResponseModel(500,"发生了未知异常");
}
/**
* 当发生MyException异常时,执行此方法
* @param myException
* @return
*/
@ExceptionHandler(MyException.class)
@ResponseBody
public ResponseModel exHandler(MyException myException) {
return new ResponseModel(500,"发生了MyException");
}
}
测试接口
/**
* @author: liumengbing
* @date: 2019/05/31 16:35
**/
@Controller
public class TestController {
@RequestMapping("/test")
@ResponseBody
public String test() {
throw new MyException();
}
}