默认的处理
浏览器访问不存在的页面
用postman访问不存在的页面
原理源码分析
springboot所有的配置都是使用自动配置的,如***AutoConfiguration来注册组件,后面需要的时候再使用
调用ErrorMvcAutoConfiguration来注册组件
- DefaultErrorAttributes
- BasicErrorController
- WhitelabelErrorViewConfiguration
这个用来给出最上面的没有任何模板情况下的一种处理,这里初始化了SpelView做为默认的错误视图
主要步骤
1. DispatchServlet的doDispatch方法
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler())
这里的ha就是RequestMappingHandlerAdapter,HandlerMethod就是BasicErrorController
2. handlerInternal返回ModelAndView
mav = invokeHandlerMethod(request, response, handlerMethod);
通过反射,调用 调到BasicErrorController的error方法
3. 这里针对Html与json有不同的处理
@RequestMapping(produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
//获得响应的状态码信息如404
HttpStatus status = getStatus(request);
//获得错误的属性信息
Map<String, Object> 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);
}
- 获得错误信息,加入到map中
这里调用 DefaultErrorAttributes#getErrorAttributes方法来获得,DefaultErrorAttributes是springboot 开始自动注入的
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, requestAttributes);
addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
addPath(errorAttributes, requestAttributes);
return errorAttributes;
}
```
- 解析错误视图,实际上调用 的是
DefaultErrorViewResolver#resolveErrorViewresolveErrorView
```java
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map<String, Object> model) {
//如果能精确找到有状态码的,优先返回
ModelAndView modelAndView = resolve(String.valueOf(status), model);
//如果没有找到精确的,那么就找系列的,即4xx,5xx
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
调用DefaultErrorViewResolver#resolve来解析渲染视图
private ModelAndView resolve(String viewName, Map<String, Object> model) {
String errorViewName = "error/" + viewName;
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
.getProvider(errorViewName, this.applicationContext);
//检查是否有模板引擎,如果有用模板引擎,使用模板引擎处理
if (provider != null) {
return new ModelAndView(errorViewName, model);
}
//没有模板引擎的处理,在静态文件目录下查找并处理
return resolveResource(errorViewName, model);
}
- 问题:没有模板引擎的处理,按理说应该会返回最开始的空白页面,可是并没有看到处理呢?
其实上面确实没有处理,上面的所有步骤都是在DispatcherServelet的主流程:
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
这里的mv其实就是错误的视图
最后会调用
processDispatchResult来处理,跟进代码
//1.调用render来渲染
render(mv, request, response)
//具体是调用SpelView.render来渲染
@Override
public void render(Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (response.getContentType() == null) {
response.setContentType(getContentType());
}
Map<String, Object> map = new HashMap<String, Object>(model);
map.put("path", request.getContextPath());
PlaceholderResolver resolver = new ExpressionResolver(getExpressions(), map);
String result = this.helper.replacePlaceholders(this.template, resolver);
response.getWriter().append(result);
}
@RequestMapping
@ResponseBody
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
//获取错误信息
Map<String, Object> body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
//检查状态
HttpStatus status = getStatus(request);
//封装成json对象
return new ResponseEntity<Map<String, Object>>(body, status);
}
如何定制错误
1. 定制页面
1)、有模板引擎的情况下;error/状态码; 【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】,发生此状态码的错误就会来到 对应的页面;
我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html);
页面能获取的信息;
timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303数据校验的错误都在这里
DefaultErrorAttributes#getErrorAttributes
private void addErrorDetails(Map<String, Object> errorAttributes,
RequestAttributes requestAttributes, boolean includeStackTrace) {
//JSR303处理bindingresult
Throwable error = getError(requestAttributes);
if (error != null) {
while (error instanceof ServletException && error.getCause() != null) {
error = ((ServletException) error).getCause();
}
errorAttributes.put("exception", error.getClass().getName());
addErrorMessage(errorAttributes, error);
if (includeStackTrace) {
addStackTrace(errorAttributes, error);
}
}
Object message = getAttribute(requestAttributes, "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);
}
}
2)、没有模板引擎(即templates目录下面没有这个错误页面的文件),静态资源文件夹下找;
如果静态文件下能找到,也显示,不过变量不会被解析
3)、以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;
2. 定制json
2.1 自定义异常处理&返回定制json数据;
@ControllerAdvice
public class MyExceptionHandler {
@ResponseBody
@ExceptionHandler(UserNotExistException.class)
public Map<String,Object> handleException(Exception e){
Map<String,Object> map = new HashMap<>();
map.put("code","user.notexist");
map.put("message",e.getMessage());
return map;
}
}
//没有自适应效果,即这个只能返回Json,并不能返回html页面。
2.2 转发到/error进行自适应响应效果处理
forward与redirect的区别??这里为什么redirect不行?to do
forward相当于重新发了一次请求,redirect只是重定向到某一个页面。
@ExceptionHandler(UserNotExistException.class)
public String handleException(Exception e, HttpServletRequest request){
Map<String,Object> map = new HashMap<>();
//传入我们自己的错误状态码 4xx 5xx,否则就不会进入定制错误页面的解析流程
/**
* Integer statusCode = (Integer) request
.getAttribute("javax.servlet.error.status_code");
*/
request.setAttribute("javax.servlet.error.status_code",500);
map.put("code","user.notexist");
map.put("message",e.getMessage());
//转发到/error
return "forward:/error";
如果不传入500的状态码,页面又会返回默认的错误页面,这是为什么呢?
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//如果这里的viewName是200,那么就会找模板引擎下的200.html,然而我们并没有这个页面,因此找不到,于是就找到了默认的处理页面
//当然我们这里可以定义其它状态码的,都 没问题。
String errorViewName = "error/" + viewName;
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
.getProvider(errorViewName, this.applicationContext);
if (provider != null) {
return new ModelAndView(errorViewName, model);
}
return resolveResource(errorViewName, model);
}
添加自己自定的数据
出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法);
1、完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;
2、页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到;
容器中DefaultErrorAttributes.getErrorAttributes();默认进行数据处理的;
自定义ErrorAttributes
//给容器中加入我们自己定义的ErrorAttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
map.put("company","atguigu");
return map;
}
}
最终的效果:响应是自适应的,可以通过定制ErrorAttributes改变需要返回的内容,