Spring/Spring Boot错误页面处理的多种方式(404 not found等)

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错误处理

  1. 自定义ErrorController
  2. 在默认资源文件夹下的error文件夹, 定义对应状态码的html文件(例如: /static/error/404.html) (这是默认 BasicErrorController的逻辑)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值