概述
DefaultErrorAttributes
是Spring boot
提供的处理错误属性的缺省工具。它主要有两个功能 :
-
处理某个业务请求遇到异常时记录异常到请求属性 –
HandlerExceptionResolver
接口定义的能力;此功能体现在方法
resolveException
的实现中。在这个方法中,它将异常添加为业务请求的一个属性,属性名称使用常量ERROR_ATTRIBUTE
的值,然后返回null ModelAndView
,告诉调用者自己未能处理该异常。常量
ERROR_ATTRIBUTE
值实为org.springframework.boot.web.servlet.error.DefaultErrorAttributes.ERROR
-
从错误页面处理请求属性中获取错误属性 –
ErrorAttributes
接口定义的能力;当用户业务请求出现异常,并且该异常未被
Spring MVC
处理,或者调用了response.sendError
,Servlet
容器就会构造并触发(forward
)一个对错误处理页面的请求,并将如下信息添加为错误页面处理请求的属性 :-
javax.servlet.error.request_uri
–String
,错误发生时所请求的URI
路径 -
javax.servlet.error.exception
–Throwable
,所发生的错误/异常 -
javax.servlet.error.message
–String
,所发生的错误/异常信息 -
javax.servlet.error.status_code
–Integer
,HTTP
协议的状态代码注意 : 以上这些内容属于
Java Servlet
规范。
然后错误页面处理
Web
控制器BasicErrorController
就会使用DefaultErrorAttributes
获取该请求中的以上属性,从而获得发生错误的业务请求的如下信息 :timestamp
– 属性信息被从请求中提取的时间status
–HTTP
状态码error
– 跟HTTP
状态码对应的原因描述文字从
HttpStatus
中获取,如果获取失败,则会使用Http Status xxx
字样(这里xxx
是HTTP
状态码)exception
– 导致请求处理失败的异常对象本身message
– 错误/异常消息errors
– 如果是一个BindingResult
异常的话,这里会是BindingResult
异常中的ObjectError
对象trace
– 错误/异常栈信息path
– 错误/异常抛出时所请求的URL
路径
-
注意 1 : 上面我们提到,业务请求异常时,
DefaultErrorAttributes
解析异常过程中会将异常记录为请求的属性org.springframework.boot.web.servlet.error.DefaultErrorAttributes.ERROR
,而Servlet
容器捕获到异常发起错误页面处理请求时,也会将异常记录为错误页面处理请求的属性javax.servlet.error.exception
,这样请求属性中就会存在两个保存异常的属性,那么DefaultErrorAttributes
会使用哪个呢?事实上,DefaultErrorAttributes
先尝试从属性org.springframework.boot.web.servlet.error.DefaultErrorAttributes.ERROR
中获取,如果获取不到,则会尝试从属性javax.servlet.error.exception
中获取。
注意 2 : 以上的描述使用 “业务请求” 和 “错误页面处理请求” 以区分这是有关系的两个不同的请求,它们使用同一份属性(
attributes
),对应同一个响应对象。
关于应用
1. 引入
以Spring Boot + Spring Web MVC
应用为例,缺省情况下,应用启动时,自动配置机制ErrorMvcAutoConfiguration
会定义两个bean
:
DefaultErrorAttributes errorAttributes()
– 定义一个DefaultErrorAttributes
组件BasicErrorController basicErrorController(ErrorAttributes errorAttributes)
– 定义一个全局缺省的错误处理控制器,使用到了上面定义的DefaultErrorAttributes
组件
2. 使用
业务请求处理中出现异常时,作为一个HandlerExceptionResolver
,DefaultErrorAttributes bean
组件逻辑被应用,从而相应异常被记录到请求属性。
错误页面请求到达时,BasicErrorController
控制器方法#errorHtml
或者#error
会使用DefaultErrorAttributes bean
组件从请求属性中获取相应的错误属性然后渲染错误页面。
源代码解析
package org.springframework.boot.web.servlet.error;
/**
* Default implementation of ErrorAttributes. Provides the following attributes
* when possible:
*
* 1. timestamp - The time that the errors were extracted
* 2. status - The status code
* 3. error - The error reason
* 4. exception - The class name of the root exception (if configured)
* 5. message - The exception message
* 6. errors - Any ObjectErrors from a BindingResult exception
* 7. trace - The exception stack trace
* 8. path - The URL path when the exception was raised
*
*
* @see ErrorAttributes
*/
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DefaultErrorAttributes
implements ErrorAttributes, HandlerExceptionResolver, Ordered {
private static final String ERROR_ATTRIBUTE = DefaultErrorAttributes.class.getName()
+ ".ERROR";
private final boolean includeException;
/**
* Create a new DefaultErrorAttributes instance that does not include the
* "exception" attribute.
*/
public DefaultErrorAttributes() {
this(false);
}
/**
* Create a new DefaultErrorAttributes instance.
* @param includeException whether to include the "exception" attribute
*/
public DefaultErrorAttributes(boolean includeException) {
this.includeException = includeException;
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
// 解析异常,具体的做法是 :
// 1. 将异常 ex 作为属性 ERROR_ATTRIBUTE 的值添加到请求 request 上
// 2. 返回 null ModelAndView (要点提示 : null 相当于告诉调用者自己未能处理该异常)
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
storeErrorAttributes(request, ex);
return null;
}
// 将异常 ex 作为属性 ERROR_ATTRIBUTE 的值添加到请求 request 上
private void storeErrorAttributes(HttpServletRequest request, Exception ex) {
request.setAttribute(ERROR_ATTRIBUTE, ex);
}
// 获取请求对象 webRequest 中所包含的错误属性的有关信息,以及当前时间,组成一个
// Map<String, Object> 对象并返回
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest,
boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, webRequest);
addErrorDetails(errorAttributes, webRequest, includeStackTrace);
addPath(errorAttributes, webRequest);
return errorAttributes;
}
// 获取请求属性对象 requestAttributes 中指定名称为 "javax.servlet.error.status_code" 的属性值,
// 试图将其转换成一个 HttpStatus, 然后将该 HttpStatus 的错误码添加为 errorAttributes 的属性
// "status",并将其对应的 reasonPhrase 字符串添加为 errorAttributes 的属性 "error" 。
// 如果不能转成 HttpStatus, 则添加的 "status"/"error"属性为 : 999/"None"
// 这里请求属性对象参数 requestAttributes 通常就是一个请求对象
private void addStatus(Map<String, Object> errorAttributes,
RequestAttributes requestAttributes) {
Integer status = getAttribute(requestAttributes,
"javax.servlet.error.status_code");
if (status == null) {
errorAttributes.put("status", 999);
errorAttributes.put("error", "None");
return;
}
errorAttributes.put("status", status);
try {
errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());
}
catch (Exception ex) {
// Unable to obtain a reason
errorAttributes.put("error", "Http Status " + status);
}
}
// 从请求 webRequest 中提取错误异常对象有关信息:根异常类名,异常消息,异常堆栈跟踪信息,
// 然后添加到 errorAttributes
private void addErrorDetails(Map<String, Object> errorAttributes,
WebRequest webRequest, boolean includeStackTrace) {
Throwable error = getError(webRequest);
if (error != null) {
while (error instanceof ServletException && error.getCause() != null) {
error = ((ServletException) error).getCause();
}
if (this.includeException) {
errorAttributes.put("exception", error.getClass().getName());
}
addErrorMessage(errorAttributes, error);
if (includeStackTrace) {
addStackTrace(errorAttributes, error);
}
}
Object message = getAttribute(webRequest, "javax.servlet.error.message");
if ((!StringUtils.isEmpty(message) || errorAttributes.get("message") == null)
&& !(error instanceof BindingResult)) {
errorAttributes.put("message",
StringUtils.isEmpty(message) ? "No message available" : message);
}
}
// 将错误 error 的错误消息提取出来作为属性 "message" 添加到 errorAttributes
private void addErrorMessage(Map<String, Object> errorAttributes, Throwable error) {
BindingResult result = extractBindingResult(error);
if (result == null) {
errorAttributes.put("message", error.getMessage());
return;
}
if (result.hasErrors()) {
errorAttributes.put("errors", result.getAllErrors());
errorAttributes.put("message",
"Validation failed for object='" + result.getObjectName()
+ "'. Error count: " + result.getErrorCount());
}
else {
errorAttributes.put("message", "No errors");
}
}
// 检查错误 error 是否属于类型 BindingResult, 如果是做强制类型转换并返回,
// 否则如果 error 类型是 MethodArgumentNotValidException, 则获取它的属性 bindingResult
// 并返回,否则返回 null
private BindingResult extractBindingResult(Throwable error) {
if (error instanceof BindingResult) {
return (BindingResult) error;
}
if (error instanceof MethodArgumentNotValidException) {
return ((MethodArgumentNotValidException) error).getBindingResult();
}
return null;
}
// 获取错误 error 的异常栈跟踪信息字符串,作为属性 "trace" 添加到 errorAttributes
private void addStackTrace(Map<String, Object> errorAttributes, Throwable error) {
StringWriter stackTrace = new StringWriter();
error.printStackTrace(new PrintWriter(stackTrace));
stackTrace.flush();
errorAttributes.put("trace", stackTrace.toString());
}
// 获取请求 requestAttributes 属性 "javax.servlet.error.request_uri" 的属性值,
// 它表示异常发生时所请求的路径,将它作为属性 "path" 添加到 errorAttributes
// 这里请求属性对象参数 requestAttributes 通常就是一个请求对象
private void addPath(Map<String, Object> errorAttributes,
RequestAttributes requestAttributes) {
String path = getAttribute(requestAttributes, "javax.servlet.error.request_uri");
if (path != null) {
errorAttributes.put("path", path);
}
}
// 获取请求对象 webRequest 属性名称为 ERROR_ATTRIBUTE 或者 "javax.servlet.error.exception"
// 的属性值,应该是一个异常
@Override
public Throwable getError(WebRequest webRequest) {
Throwable exception = getAttribute(webRequest, ERROR_ATTRIBUTE);
if (exception == null) {
exception = getAttribute(webRequest, "javax.servlet.error.exception");
}
return exception;
}
// 获取请求属性对象 requestAttributes 中指定名称为 name 的属性的值,
// 这里请求属性对象参数 requestAttributes 通常就是一个请求对象
@SuppressWarnings("unchecked")
private <T> T getAttribute(RequestAttributes requestAttributes, String name) {
return (T) requestAttributes.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
}
}