赞
踩
在日常开发中,假如我们访问一个Sping容器中并不存在的路径,通常会返回404的报错,具体原因是什么呢?
错误的访问会调用两次DispatcherServlet:第一次调用无法找到对应路径时,会给Response设置一个错误状态,第二次是根据这个状态执行预先设置了error属性的DispatcherServlet。而正确的访问只会调用一次DispatcherServlet。
我们知道,基于SpringMvc原理的Spring Boot项目,所有的路由请求默认都是由DispatcherServlet类来负责处理的?
如果开启断点调试会发现在第二次进入DispatcherServlet的doDispatch方法时,便直接返回了404错误:
那么问题的重点应该出现在两次DispatcherServlet的调用上,通过调试可以发现,最后一次的调用分析的意义不大,因为从它的request属性就能看出来,它预先设置了一堆的错误属性,明显就是为了返回错误,走了一遍DispatcherServlet的标准流程。
重点只有两点:
第一次执行DispatcherServlet的过程中发生了什么?
错误的请求为什么会有两次DispatcherServlet调用?
通过断点调试,可以看到在执行DispatcherServlet中的doDispatch方法的时候进入了HttpRequestHandlerAdapter的handle方法
- public class HttpRequestHandlerAdapter implements HandlerAdapter {
- public HttpRequestHandlerAdapter() {
- }
- public boolean supports(Object handler) {
- return handler instanceof HttpRequestHandler;
- }
- @Nullable
- public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- ((HttpRequestHandler)handler).handleRequest(request, response);
- return null;
- }
- ......
接着又执行了ResourceHttpRequestHandler的handleRequest方法,在执行的过程中,在容器中没有找到对应的路径,所以对response设置了404错误:
也就是
response.sendError(404)
它的底层实际上是给Response类的一个私有属性errorState,设置了错误状态:
- public boolean setError() {
- return this.errorState.compareAndSet(0, 1);
- }
这样设置有什么用呢?
这就涉及到第二次执行DispatcherServlet的原因了。
重点在StandardHostValve类的invoke方法中:
- public final void invoke(Request request, Response response) throws IOException, ServletException {
- ......
- try {
- //第一次执行DispatcherServlet的原因
- if (!response.isErrorReportRequired()) {
- //执行正常的请求
- context.getPipeline().getFirst().invoke(request, response);
- }
- } catch (Throwable var10) {
- ExceptionUtils.handleThrowable(var10);
- this.container.getLogger().error("Exception Processing " + request.getRequestURI(), var10);
- if (!response.isErrorReportRequired()) {
- request.setAttribute("javax.servlet.error.exception", var10);
- this.throwable(request, response, var10);
- }
- }
-
- response.setSuspended(false);
- Throwable t = (Throwable)request.getAttribute("javax.servlet.error.exception");
- if (context.getState().isAvailable()) {
- //第二次执行DispatcherServlet的原因
- if (response.isErrorReportRequired()) {
- AtomicBoolean result = new AtomicBoolean(false);
- response.getCoyoteResponse().action(ActionCode.IS_IO_ALLOWED, result);
- if (result.get()) {
- if (t != null) {
- this.throwable(request, response, t);
- } else {
- //执行错误请求
- this.status(request, response);
- }
- }
- }
- ......
两次执行的原因是因为
response.isErrorReportRequired()
- public class Response implements HttpServletResponse {
- public boolean isErrorReportRequired() {
- return this.getCoyoteResponse().isErrorReportRequired();
- }
- ......
-
- public final class Response {
-
- public boolean isErrorReportRequired() {
- return this.errorState.get() == 1;
- }
- .....
也就是根据Response类的私有属性errorState来判断的。
第一次执行DispatcherServlet的时候,由于errorState的值还是初始化值0,所以可以正常执行,执行的时候找不到对应的路径资源,便执行了response.sendError(404)方法,给errorState赋值为1。
这是StandardHostValve类中,invoke执行的
context.getPipeline().getFirst().invoke(request, response);
给errorState赋值1以后,又执行了:
this.status(request, response);
它的执行链路是这样:
status -> custom -> forward -> doForward -> processRequest ->doFilter。
也就是对本次请求执行了一次转发,最后重新调用了一遍
filterChain.doFilter(request, response)
的流程,走了错误调用。
在processRequest方法可以比较看的比较明确:
- private void processRequest(ServletRequest request, ServletResponse response, State state) throws IOException, ServletException {
- DispatcherType disInt = (DispatcherType)request.getAttribute("org.apache.catalina.core.DISPATCHER_TYPE");
- if (disInt != null) {
- boolean doInvoke = true;
- if (this.context.getFireRequestListenersOnForwards() && !this.context.fireRequestInitEvent(request)) {
- doInvoke = false;
- }
-
- if (doInvoke) {
- if (disInt != DispatcherType.ERROR) {
- state.outerRequest.setAttribute("org.apache.catalina.core.DISPATCHER_REQUEST_PATH", this.getCombinedPath());
- state.outerRequest.setAttribute("org.apache.catalina.core.DISPATCHER_TYPE", DispatcherType.FORWARD);
- this.invoke(state.outerRequest, response, state);
- } else {
- this.invoke(state.outerRequest, response, state);
- }
-
- if (this.context.getFireRequestListenersOnForwards()) {
- this.context.fireRequestDestroyEvent(request);
- ......
等到DispatcherServlet再执行完一次,便能在浏览器看到404报错了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。