Spring/Spring Boot错误页面处理的多种方式(404 not found等)
简单来说, tomcat容器会根据error-page的内容forward到指定location(会再走一次DIspatcherServlet).
注意本文章只分析类似于404错误码(比如HttpServletResponse#sendError), 异常处理可以参考@ControllerAdvice + @ExceptionHandler
前言
1.DispatcherServlet找不到资源的情况(sendError发生)
- 情况一, 第6行, 找不到对应的映射器来处理.
public class DispatcherServlet extends FrameworkServlet {
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//...
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return; // 提前返回
}
//...
}
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 默认false(不抛异常), 调用sendError
if (this.throwExceptionIfNoHandlerFound) {
throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
new ServletServerHttpRequest(request).getHeaders());
}
else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
}
- 情况二, 在其他地方找不到资源.比如说配置了默认资源拦截器(“/**”), 看第7行和第21行, 最终会sendError 或 forward(交由容器的默认servlet处理)
// 省略了部分代码, 这两个类可作为资源处理(具体看另外文章:"Spring静态资源处理多种方式")
public class ResourceHttpRequestHandler implements HttpRequestHandler {
public void handleRequest(HttpServletRequest request, HttpServletResponse response) {
Resource resource = getResource(request);
if (resource == null) {
logger.debug("Resource not found");
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
//...
}
}
public class DefaultServletHttpRequestHandler implements HttpRequestHandler, ServletContextAware {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response) {
RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName);
rd.forward(request, response);
}
}
2.tomcat处理error-page(sendError后续)
error-page可以定义errorCode/exceptionType/location(可在web.xml中定义)
在sendError后, 找到对应ErrorPage(第11行), forward到对应location(第21,23行)
<error-page>
<location>/err.html</location>
</error-page>
//省略部分代码, response#sendError后,会设置errorState(参考tomcat的ResponseFacade)
final class StandardHostValve extends ValveBase {
public final void invoke(Request request, Response response) {
//... 查看errorState;
if (response.isErrorReportRequired()) {
status(request, response);
}
}
private void status(Request request, Response response) {
//... 根据HTTP状态码寻找error-page, 找不到再找状态码为"0"
ErrorPage errorPage = context.findErrorPage(statusCode);
if (errorPage == null) {
// Look for a default error page
errorPage = context.findErrorPage(0);
}
//...
if (custom(request, response, errorPage)) {}
}
private boolean custom(Request request, Response response, ErrorPage errorPage) {
RequestDispatcher rd =
servletContext.getRequestDispatcher(errorPage.getLocation());
//...
rd.forward(request.getRequest(), response.getResponse());
}
}
3.SpringBoot定义error-page
spring boot没有web.xml, 默认内嵌了tomcat, 参考ErrorMvcAutoConfiguration
.
该ErrorPage默认location=“/error”(参考:ErrorProperties, 可自定义:server.error.path)
// 基于源码spring boot 2.7.2
public class ErrorMvcAutoConfiguration {
// 定义了默认的ErrorPage#location="/error"
@Bean
public ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {
return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);
}
// 定义默认的Controller, 处理"/error" (可自定义, 参考第10行)
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
ObjectProvider<ErrorViewResolver> errorViewResolvers) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
errorViewResolvers.orderedStream().collect(Collectors.toList()));
}
}
默认的Controller处理“/error” (可自定义, 参考上面第10行)
看下面注释
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
//...@RequestMapping..., 返回ModelAndView, 参考下面类
}
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
// 简单来说, 会找到默认资源目录下的error文件夹下的404.html(xxxCode.html) (第11, 20行)
// 默认资源目录:classpath:/META-INF/resources/,/resources/,/static/,/public/
private ModelAndView resolve(String viewName, Map<String, Object> model) {
String errorViewName = "error/" + viewName;
//...
return resolveResource(errorViewName, model);
}
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
for (String location : this.resources.getStaticLocations()) {
try {
Resource resource = this.applicationContext.getResource(location);
resource = resource.createRelative(viewName + ".html");
if (resource.exists()) {
return new ModelAndView(new HtmlResourceView(resource), model);
}
}
catch (Exception ex) {
}
}
return null;
}
}
一: Spring错误处理
在web.xml中定义<error-page>
二: springBoot错误处理
- 自定义ErrorController
- 在默认资源文件夹下的error文件夹, 定义对应状态码的html文件(例如: /static/error/404.html) (这是默认 BasicErrorController的逻辑)