浏览器访问不存在资源效果

一、底层分析
1.ErrorPageCustomizer 错误页面定制器
public class ErrorMvcAutoConfiguration {
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
// this.properties.getError().getPath() = /error
ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage});
}
}
当应用出现了4xx或5xx之类的错误 ,ErrorPageCustomizer就会被激活,它主要 用于定制错误 处理的响应规则,就会发送一个/error请求,它会交给 BasicErrorController进行处理
2.BasicErrorController 就会接收 /error 请求处理
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
//通过请求头判断调用下面哪个访求: text/html
@RequestMapping(
produces = {"text/html"}
)
//接受error请求,然后响应错误页面
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = this.getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
//去哪个页面作为错误页面,包括 页面地址与页面内容,里面有一个ErrorViewResolver
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
//没有找到,则找 error 视图 ,在ErrorMvcAutoConfiguration的defaultErrorView中
return modelAndView != null ? modelAndView : new ModelAndView("error", model);
}
@RequestMapping
//接受error请求,然后响应json数据
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = this.getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity(status);
} else {
Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
return new ResponseEntity(body, status);
}
}
}
3.DefaultErrorViewResolver 解析响应错误页面
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
//根据状态码查找对应的页面
ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
//匹配4xx、5xx页面
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//error目录下的页面
String errorViewName = "error/" + viewName;
//先查找模板引擎下的页面
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
//如果目标引擎有该页面,则返回指定视图,否则去静态资源目录下找对应错误码的页面
return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
}
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
//获取四个静态资源目录"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"
String[] var3 = this.resourceProperties.getStaticLocations();
int var4 = var3.length;
for(int var5 = 0; var5 < var4; ++var5) {
String location = var3[var5];
try {
Resource resource = this.applicationContext.getResource(location);
resource = resource.createRelative(viewName + ".html");//如:404.html
if (resource.exists()) {
return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model);
}
} catch (Exception var8) {
}
}
return null;
}
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
static {
//定义4xx、5xx页面
Map<Series, String> views = new EnumMap(Series.class);
views.put(Series.CLIENT_ERROR, "4xx");
views.put(Series.SERVER_ERROR, "5xx");
SERIES_VIEWS = Collections.unmodifiableMap(views);
}
}
4.DefaultErrorAttributes 错误页面可获取到的数据信息
通过 BasicErrorController 的方法中响应的 module 可定位到响应哪些数据,从而引出 ErrorAttributes 的实现类 DefaultErrorAttributes , DefaultErrorAttributes 中绑定的所有值都可在页面获取到。
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = this.getStatus(request);
//this.getErrorAttributes 调用了父类的方法
Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
return modelAndView != null ? modelAndView : new ModelAndView("error", model);
}
}
BasicErrorController父类:AbstractErrorController
public abstract class AbstractErrorController implements ErrorController {
private final ErrorAttributes errorAttributes;
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
WebRequest webRequest = new ServletWebRequest(request);
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
}
}
ErrorAttributes接口的实现类:DefaultErrorAttributes
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap();
//添加时间戳到map "timestamp"
errorAttributes.put("timestamp", new Date());
this.addStatus(errorAttributes, webRequest);
this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
this.addPath(errorAttributes, webRequest);
return errorAttributes;
}
}
可获取数据的字段:
- timestamp:时间戳
- status:状态码
- error:错误提示
- exception:异常对象
- message:异常消息
- errors:JSR303数据校验出现的错
模板引擎错误页面可以获取这些数据
5. 模板目录与静态目录下都找不到对应错误页面,就响应 SpringBoot 默认的错误页面
通过 BasicErrorController 的 errorhtml 方法后 一行可知,没有找到则找 error 视图对象 ,error定义 在 ErrorMvcAutoConfiguration 的 defaultErrorView 中
ErrorMvcAutoConfiguration类
protected static class WhitelabelErrorViewConfiguration {
//ErrorMvcAutoConfiguration.StaticView拼接错误信息
private final ErrorMvcAutoConfiguration.StaticView defaultErrorView = new ErrorMvcAutoConfiguration.StaticView();
protected WhitelabelErrorViewConfiguration() {
}
ErrorMvcAutoConfiguration.StaticView内部类,拼接错误信息
private static class StaticView implements View {
private StaticView() {
}
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
if (response.isCommitted()) {
String message = this.getMessage(model);
logger.error(message);
} else {
response.setContentType(TEXT_HTML_UTF8.toString());
StringBuilder builder = new StringBuilder();
Date timestamp = (Date)model.get("timestamp");
Object message = model.get("message");
Object trace = model.get("trace");
if (response.getContentType() == null) {
response.setContentType(this.getContentType());
}
builder.append("<html><body><h1>Whitelabel Error Page</h1>").append("<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>").append("<div id='created'>").append(timestamp).append("</div>").append("<div>There was an unexpected error (type=").append(this.htmlEscape(model.get("error"))).append(", status=").append(this.htmlEscape(model.get("status"))).append(").</div>");
if (message != null) {
builder.append("<div>").append(this.htmlEscape(message)).append("</div>");
}
if (trace != null) {
builder.append("<div style='white-space:pre-wrap;'>").append(this.htmlEscape(trace)).append("</div>");
}
builder.append("</body></html>");
response.getWriter().append(builder.toString());
}
}
二、自定义数据进行响应
出现错误以后,会发送 /error 请求,会被 BasicErrorController 处理,而响应的数据是DefaultErrorAttributes.getErrorAttributes 封装的, 所以我们只需要自定义 ErrorAttributes 实现类即可
1.自定义一个类继承DefaultErrorAttributes
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
//保留之前的数据
Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
//添加自定义的数据
map.put("test", "测试数据........");
return map;
}
}
2.错误页面获取数据测试
<body>
404页面
<p>[[${timestamp}]]</p>
<p>[[${test}]]</p>
</body>
三、自定义错误拦截器
异常处理类
@ControllerAdvice //拦截所有标注@Controller的控制器
public class ControllerExceptionHandler {
private Logger logger = LoggerFactory.getLogger(this.getClass());
//该注解表示这个方法可以异常处理,拦截的异常为Exception或Exception的子异常
@ExceptionHandler(Exception.class)
public ModelAndView exceptionHandler(HttpServletRequest request, Exception e){
//输出错误信息
logger.error("Request URL:{}, Exception: {}",request.getRequestURL(),e);
ModelAndView mv = new ModelAndView();
mv.addObject("url",request.getRequestURL());
mv.addObject("exception",e);
//转发到前端error页面
mv.setViewName("error/error");
return mv;
}
}
错误页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>error</title>
</head>
<body>
<h3>Error</h3>
<div>
<!--th:utext:转义字符串,th:remove移除标签,以注释的方式输出字符串-->
<div th:utext="'<!--'" th:remove="tag"></div>
<div th:utext="'Failed Request URL : ' + ${url}" th:remove="tag"></div>
<div th:utext="'Exception message : ' + ${exception.message}" th:remove="tag"></div>
<ul th:remove="tag">
<li th:each="st : ${exception.stackTrace}" th:remove="tag"><span th:utext="${st}" th:remove="tag"></span></li>
</ul>
<div th:utext="'-->'" th:remove="tag"></div>
</div>
</body>
</html>