作者:一一哥
我们在发送请求的时候,如果发生了404异常,SpringBoot是怎么处理的呢?
我们可以随便发送一个不存在的请求来验证一下,就会看到如下图所示:
当服务器内部发生代码等错误的时候,会发生什么呢?
比如我们人为的制造一个异常出来,如下面的代码所示:
@GetMapping("/user/{id:\\d+}")
public User get(@PathVariable String id) {
throw new RuntimeException();
}
结果产生了如下所示效果图:
timestamp: 时间戳.
status: 状态码.
error: 错误提示.
exception: 异常对象.
message: 异常消息.
errors: 数据效验相关的信息.
通过上面的两个案例,我们发现无论发生了什么错误,Spring Boot都会返回一个对应的状态码以及一个错误页面,那么这个错误页面是怎么来的呢?
要弄明白这个问题,我们需要从Spring Boot中错误处理的底层源码来进行分析。
SpringBoot的错误配置信息是通过ErrorMvcAutoConfiguration这个类来进行配置的,这个类中帮我们注册了以下组件:
DefaultErrorAttributes: 帮我们在页面上共享错误信息;
ErrorPageCustomizer: 项目中发生错误后,该对象就会生效,用来定义请求规则;
BasicErrorController: 处理默认的 ’/error‘ 请求,分为两种处理请求方式:一种是html方式,一种是json方式;
DefaultErrorViewResolver: 默认的错误视图解析器,将错误信息解析到相应的错误视图.
static文件夹存放的是静态页面,它没有办法使用模板引擎表达式.
1️⃣. 当出现4xx或5xx的错误:ErrorPageCustomizer开始生效;
2️⃣. 然后ErrorPageCustomizer发起 /error 请求;
3️⃣. /error 请求被BasicErrorController处理;
4️⃣. BasicErrorController根据请求头中的Accept决定如何响应处理.
其实在以上的所有类中,最重要的一个类就是BasicErrorController,所以接下来我们来分析BasicErrorController源码,看看Spring Boot底层到底是怎么实现error处理的。
在Spring Boot中,当发生了错误后,默认情况下,Spring Boot提供了一个处理程序出错的结果映射路径 /error
。当发生错误后,Spring Boot就会将请求转发到BasicErrorController控制器来处理这个错误请求。
所以我们重点分析BasicErrorController源码,首先呈上源码内容:
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
private final ErrorProperties errorProperties;
/**
* Create a new {@link BasicErrorController} instance.
* @param errorAttributes the error attributes
* @param errorProperties configuration properties
*/
public BasicErrorController(ErrorAttributes errorAttributes,
ErrorProperties errorProperties) {
this(errorAttributes, errorProperties,
Collections.emptyList());
}
/**
* Create a new {@link BasicErrorController} instance.
* @param errorAttributes the error attributes
* @param errorProperties configuration properties
* @param errorViewResolvers error view resolvers
*/
public BasicErrorController(ErrorAttributes errorAttributes,
ErrorProperties errorProperties, List errorViewResolvers) {
super(errorAttributes, errorViewResolvers);
Assert.notNull(errorProperties, "ErrorProperties must not be null");
this.errorProperties = errorProperties;
}
@Override
public String getErrorPath() {
return this.errorProperties.getPath();
}
@RequestMapping(produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map model = Collections.unmodifiableMap(getErrorAttributes(
request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
}
@RequestMapping
@ResponseBody
public ResponseEntity
这个源码的注释信息中说明了,这是一个Spring Boot自带的全局错误Controller.
这个Controller中有一个RequestMapping注解,内部有一个相当于三元运算符的操作。如果你在配置文件配置了server.error.path
的话,就会使用你配置的异常处理地址,如果没有就会使用你配置的error.path
路径地址,如果还是没有,则默认使用/error
来作为发生异常时的处理地址。
所以我们可以按照如下图中的配置,来设置自定义的错误处理页面。
从上面的源码我们可以看到,BasicErrorController中有两个RequestMapping方法,分别是errorHtml()与error()方法来处理错误请求。
那么为什么会是两个呢?请看下面的截图:
BasicErrorController内部是通过读取request请求头中的Accept
属性值,判断其内容是否为text/html
;然后以此来区分请求到底是来自于浏览器(浏览器通常默认自动发送请求头内容Accept:text/html
),还是客户端,从而决定是返回一个页面视图,还是返回一个 JSON 消息内容。
可以看到其中errorHtml()方法是用来处理浏览器发送来的请求,它会产生一个白色标签样式(whitelabel)的错误视图页面,该视图将以HTML格式渲染出错误数据。
该方法返回了一个error页面,如果你的项目静态页面下刚好存在一个error所对应的页面,那么Spring Boot会得到你本地的页面,如下图:
而error()方法用来处理来自非浏览器,也就是其他软件app客户端(比如postman等)发送来的错误请求,它会产生一个包含详细错误,HTTP状态及其他异常信息的JSON格式的响应内容。
BasicErrorController可以作为自定义ErrorController的基类,我们只需要继承BasicErrorController,添加一个public方法,并添加@RequestMapping注解,让该注解带有produces属性,然后创建出该新类型的bean即可。
经过上面的源码分析得知,在默认情况下,Spring Boot为这两种情况提供了不同的响应方式.
一种是浏览器客户端请求一个不存在的页面或服务端处理发生异常时,这时候一般Spring Boot默认会响应一个html文档内容,称作“Whitelabel Error Page”:
另一种是当我们使用Postman等调试工具发送请求一个不存在的url或服务端处理发生异常时,Spring Boot会返回类似如下的一个 JSON 字符串信息:
{
"timestamp": "2019-05-20T06:12:45.209+0000",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/login.html"
}