在springboot中默认有一个异常处理器接口 ErrorContorller
,该接口提供了 getErrorPath()
方法,此接口的 BasicErrorController
实现类实现了 getErrorPath()
方法,如下:
/* * AbstractErrorController是ErrorContoller的实现类 */ @Controller @RequestMapping("${server.error.path:${error.path:/error}}") public class BasicErrorController extends AbstractErrorController { private final ErrorProperties errorProperties; ... @Override public String getErrorPath() { return this.errorProperties.getPath(); } @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { 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) ? modelAndView : new ModelAndView("error", model); } @RequestMapping public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { HttpStatus status = getStatus(request); if (status == HttpStatus.NO_CONTENT) { return new ResponseEntity<>(status); } Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL)); return new ResponseEntity<>(body, status); } .... }
errorProperties
类定义如下:
public class ErrorProperties { /** * Path of the error controller. */ @Value("${error.path:/error}") private String path = "/error"; ... }
由此可见,springboot中默认有一个处理 /error
映射的控制器,有 error
和 errorHtml
两个方法的存在,它可以处理来自浏览器页面和来自机器客户端(app应用)的请求。
当用户请求不存在的url时, dispatcherServlet
会交由 ResourceHttpRequestHandler
映射处理器来处理该请求,并在 handlerRequest
方法中,重定向至 /error
映射,代码如下:
@Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // For very general mappings (e.g. "/") we need to check 404 first Resource resource = getResource(request); if (resource == null) { logger.debug("Resource not found"); response.sendError(HttpServletResponse.SC_NOT_FOUND); // 404 return; } ... }
getResource(request)
会调用 this.resolverChain.resolveResource(request, path, getLocations())
方法, getLocations()
方法返回结果如下:
接着程序会执行到 getResource(pathToUse, location)
方法如下:
@Nullable protected Resource getResource(String resourcePath, Resource location) throws IOException { // 新建一个resource对象,url为 location + resourcePath,判断此对象(文件)是否存在 Resource resource = location.createRelative(resourcePath); if (resource.isReadable()) { if (checkResource(resource, location)) { return resource; } else if (logger.isWarnEnabled()) { Resource[] allowedLocations = getAllowedLocations(); logger.warn("Resource path /"" + resourcePath + "/" was successfully resolved " + "but resource /"" + resource.getURL() + "/" is neither under the " + "current location /"" + location.getURL() + "/" nor under any of the " + "allowed locations " + (allowedLocations != null ? Arrays.asList(allowedLocations) : "[]")); } } return null; }
在resource.isReadable()
中,程序会在locations目录中寻找path目录下文件,由于不存在,返回null。
最终也就导致程序重定向至/error映射,如果是来自浏览器的请求,也就会返回 /template/error/404.html
页面,所以对于404请求,只需要在template目录下新建error目录,放入404页面即可。
ControllerAdvice
注解 + ExceptionHandler
注解来助理不同错误类型的异常,但在springboot中404异常和拦截器异常由spring自己处理。