转载

springboot异常处理之404

源码分析

在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 映射的控制器,有 errorerrorHtml 两个方法的存在,它可以处理来自浏览器页面和来自机器客户端(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() 方法返回结果如下:

springboot异常处理之404

接着程序会执行到 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页面即可。

使用注意

  1. 在springboot4.x中我们可以自定义 ControllerAdvice 注解 + ExceptionHandler 注解来助理不同错误类型的异常,但在springboot中404异常和拦截器异常由spring自己处理。
原文  https://segmentfault.com/a/1190000022174661
正文到此结束
Loading...