springboot异常处理之404

ps: 推荐一下本人的通用后台管理项目crowd-admin 以及newbee-mall增强版,喜欢的话给个star就好

开始这篇博文的时候我们先回顾下spring异常处理的几种机制

异常处理机制

  • @ExceptionHandle 注解处理异常

    处理单个controller内部抛出的指定异常

  • @ControllerAdvice+@ExceptionHandler 注解处理异常

    处理所有controller内部抛出的指定异常

  • 自定义 HandlerExceptionResolver 类处理异常

    全局异常处理

事件经过

​ 先说博主最近遇到的问题,在老的spring项目中用@RestControllerAdvice注解定义的全局异常处理类是按如下方式来处理404请求的:

/**
     * 处理404异常
     *
     * @param e
     * @param request
     * @return
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    public Object handle404Exception(NoHandlerFoundException e, HttpServletRequest request) {
        logger.error(e.getMessage(), e);
        if (HttpUtil.isAjax(request)) {
            return Response.error("您请求路径不存在,请检查url!");
        }
        return new ModelAndView("error/404");
    }

但是在新的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()方法返回结果如下:

接着程序会执行到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自己处理。
posted @ 2021-01-14 16:15  wayn111  阅读(1465)  评论(0编辑  收藏  举报