关于spring:SpringMVC-源码分析之-FrameworkServlet

8次阅读

共计 8790 个字符,预计需要花费 22 分钟才能阅读完成。

后面和小伙伴们聊了 SpringMVC 的初始化流程,置信大家对于 SpringMVC 的初始化过程都有一个根本认知了,明天咱们就来看看当一个申请达到后,它的执行流程是什么样的?当然这个流程比拟长,松哥这里可能会分两篇文章来和大家分享。

很多小伙伴都晓得 SpringMVC 的外围是 DispatcherServlet,而 DispatcherServlet 的父类就是 FrameworkServlet,因而咱们先来看看 FrameworkServlet,这有助于咱们了解 DispatcherServlet。

1.FrameworkServlet

FrameworkServlet 继承自 HttpServletBean,而 HttpServletBean 继承自 HttpServlet,HttpServlet 就是 JavaEE 里边的货色了,这里咱们不做探讨,从 HttpServletBean 开始就是框架的货色了,然而 HttpServletBean 比拟非凡,它的非凡在于它没有进行任何的申请解决,只是参加了一些初始化的操作,这些比较简单,而且咱们在上篇文章中也曾经剖析过了,所以这里咱们对 HttpServletBean 不做剖析,就间接从它的子类 FrameworkServlet 开始看起。

和所有的 Servlet 一样,FrameworkServlet 对申请的解决也是从 service 办法开始,咱们先来看看该办法 FrameworkServlet#service:

@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
    if (httpMethod == HttpMethod.PATCH || httpMethod == null) {processRequest(request, response);
    }
    else {super.service(request, response);
    }
}

能够看到,在该办法中,首先获取到以后申请办法,而后对 patch 申请额定关照了下,其余类型的申请通通都是 super.service 进行解决。

然而在 HttpServlet 中并未对 doGet、doPost 等申请进行实质性解决,所以 FrameworkServlet 中还重写了各种申请对应的办法,如 doDelete、doGet、doOptions、doPost、doPut、doTrace 等,其实就是除了 doHead 之外的其余办法都重写了。

咱们先来看看 doDelete、doGet、doPost 以及 doPut 四个办法:

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {processRequest(request, response);
}
@Override
protected final void doPut(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {processRequest(request, response);
}
@Override
protected final void doDelete(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {processRequest(request, response);
}

能够看到,这里又把申请交给 processRequest 去解决了,在 processRequest 办法中则会进一步调用到 doService,对不同类型的申请分类解决。

doOptions 和 doTrace 则略微有些差别,如下:

@Override
protected void doOptions(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {if (this.dispatchOptionsRequest || CorsUtils.isPreFlightRequest(request)) {processRequest(request, response);
        if (response.containsHeader("Allow")) {return;}
    }
    super.doOptions(request, new HttpServletResponseWrapper(response) {
        @Override
        public void setHeader(String name, String value) {if ("Allow".equals(name)) {value = (StringUtils.hasLength(value) ? value + "," : "") + HttpMethod.PATCH.name();}
            super.setHeader(name, value);
        }
    });
}
@Override
protected void doTrace(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {if (this.dispatchTraceRequest) {processRequest(request, response);
        if ("message/http".equals(response.getContentType())) {return;}
    }
    super.doTrace(request, response);
}

能够看到这两个办法的解决多了一层逻辑,就是去抉择是在以后办法中解决对应的申请还是交给父类去解决,因为 dispatchOptionsRequest 和 dispatchTraceRequest 变量默认都是 false,因而默认状况下,这两种类型的申请都是交给了父类去解决。

2.processRequest

咱们再来看 processRequest,这算是 FrameworkServlet 的外围办法了:

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {long startTime = System.currentTimeMillis();
    Throwable failureCause = null;
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
    initContextHolders(request, localeContext, requestAttributes);
    try {doService(request, response);
    }
    catch (ServletException | IOException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (Throwable ex) {
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    }
    finally {resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {requestAttributes.requestCompleted();
        }
        logResult(request, response, failureCause, asyncManager);
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

这个办法尽管比拟长,然而其实它的外围就是最两头的 doService 办法,以 doService 为界,咱们能够将该办法的内容分为三局部:

  1. doService 之前次要是一些筹备工作,筹备工作次要干了两件事,第一件事就是从 LocaleContextHolder 和 RequestContextHolder 中别离获取它们原来保留的 LocaleContext 和 RequestAttributes 对象存起来,而后别离调用 buildLocaleContext 和 buildRequestAttributes 办法获取到以后申请的 LocaleContext 和 RequestAttributes 对象,再通过 initContextHolders 办法将以后申请的 LocaleContext 和 RequestAttributes 对象别离设置到 LocaleContextHolder 和 RequestContextHolder 对象中;第二件事则是获取到异步管理器并设置拦截器。
  2. 接下来就是 doService 办法,这是一个形象办法,具体的实现在 DispatcherServlet 中,这个松哥放到 DispatcherServlet 中再和大家剖析。
  3. 第三局部就是 finally 中,这个里边干了两件事:第一件事就是将 LocaleContextHolder 和 RequestContextHolder 中对应的对象复原成原来的样子(参考第一步);第二件事就是通过 publishRequestHandledEvent 办法公布一个 ServletRequestHandledEvent 类型的音讯。

通过下面的剖析,大家发现,processRequest 其实次要做了两件事,第一件事就是对 LocaleContext 和 RequestAttributes 的解决,第二件事就是公布事件。咱们对这两件事别离来钻研。

2.1 LocaleContext 和 RequestAttributes

LocaleContext 和 RequestAttributes 都是接口,不同的是里边寄存的对象不同。

2.1.1 LocaleContext

LocaleContext 里边寄存着 Locale,也就是本地化信息,如果咱们须要反对国际化,就会用到 Locale。

国际化的时候,如果咱们须要用到 Locale 对象,第一反馈就是从 HttpServletRequest 中获取,像上面这样:

Locale locale = req.getLocale();

然而大家晓得,HttpServletRequest 只存在于 Controller 中,如果咱们想要在 Service 层获取 HttpServletRequest,就得从 Controller 中传参数过去,这样就比拟麻烦,特地是有的时候 Service 中相干办法都曾经定义好了再去批改,就更头大了。

所以 SpringMVC 中还给咱们提供了 LocaleContextHolder,这个工具就是用来保留以后申请的 LocaleContext 的。当大家看到 LocaleContextHolder 时不晓得有没有感觉眼生,松哥在之前的 Spring Security 系列教程中和大家聊过 SecurityContextHolder,这两个的原理基本一致,都是基于 ThreadLocal 来保留变量,进而确保不同线程之间互不烦扰,对 ThreadLocal 不相熟的小伙伴,能够看看松哥的 Spring Security 系列,之前有详细分析过(公号后盾回复 ss)。

有了 LocaleContextHolder 之后,咱们就能够在任何中央获取 Locale 了,例如在 Service 中咱们能够通过如下形式获取 Locale:

Locale locale = LocaleContextHolder.getLocale();

下面这个 Locale 对象实际上就是从 LocaleContextHolder 中的 LocaleContext 里边取出来的。

须要留神的是,SpringMVC 中还有一个 LocaleResolver 解析器,所以后面 req.getLocale() 并不总是获取到 Locale 的值,这个松哥在当前的文章中再和小伙伴们细聊。

2.1.2 RequestAttributes

RequestAttributes 是一个接口,这个接口能够用来 get/set/remove 某一个属性。

RequestAttributes 有诸多实现类,默认应用的是 ServletRequestAttributes,通过 ServletRequestAttributes,咱们能够 getRequest、getResponse 以及 getSession。

在 ServletRequestAttributes 的具体实现中,会通过 scope 参数判断操作 request 还是操作 session(如果小伙伴们不记得 Spring 中的作用域问题,能够公号后盾回复 spring,看看松哥录制的收费的 Spring 入门教程,里边有讲),咱们来看一下 ServletRequestAttributes#setAttribute 办法(get/remove 办法执行逻辑相似):

public void setAttribute(String name, Object value, int scope) {if (scope == 0) {if (!this.isRequestActive()) {throw new IllegalStateException("Cannot set request attribute - request is not active anymore!");
        }
        this.request.setAttribute(name, value);
    } else {HttpSession session = this.obtainSession();
        this.sessionAttributesToUpdate.remove(name);
        session.setAttribute(name, value);
    }
}

能够看到,这里会先判断 scope,scope 为 0 就操作 request,scope 为 1 就操作 session。如果操作的是 request,则须要首先通过 isRequestActive 办法判断以后 request 是否执行结束,如果执行结束,就不能够再对其进行其余操作了(当执行了 finally 代码块中的 requestAttributes.requestCompleted 办法后,isRequestActive 就会返回 false)。

和 LocaleContext 相似,RequestAttributes 被保留在 RequestContextHolder 中,RequestContextHolder 的原理也和 SecurityContextHolder 相似,这里不再赘述。

看了下面的解说,大家应该发现了,在 SpringMVC 中,如果咱们须要在 Controller 之外的其余中央应用 request、response 以及 session,其实不必每次都从 Controller 中传递 request、response 以及 session 等对象,咱们齐全能够间接通过 RequestContextHolder 来获取,像上面这样:

ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = servletRequestAttributes.getRequest();
HttpServletResponse response = servletRequestAttributes.getResponse();

是不是十分 easy!

2.2 事件公布

最初就是 processRequest 办法中的事件公布了。

在 finally 代码块中会调用 publishRequestHandledEvent 办法发送一个 ServletRequestHandledEvent 类型的事件,具体发送代码如下:

private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response,
        long startTime, @Nullable Throwable failureCause) {if (this.publishEvents && this.webApplicationContext != null) {
        // Whether or not we succeeded, publish an event.
        long processingTime = System.currentTimeMillis() - startTime;
        this.webApplicationContext.publishEvent(
                new ServletRequestHandledEvent(this,
                        request.getRequestURI(), request.getRemoteAddr(),
                        request.getMethod(), getServletConfig().getServletName(),
                        WebUtils.getSessionId(request), getUsernameForRequest(request),
                        processingTime, failureCause, response.getStatus()));
    }
}

能够看到,事件的发送须要 publishEvents 为 true,而该变量默认就是 true。如果须要批改该变量的值,能够在 web.xml 中配置 DispatcherServlet 时,通过 init-param 节点顺便配置一下该变量的值。失常状况下,这个事件总是会被发送进来,如果我的项目有须要,咱们能够监听该事件,如下:

@Component
public class ServletRequestHandleListener implements ApplicationListener<ServletRequestHandledEvent> {
    @Override
    public void onApplicationEvent(ServletRequestHandledEvent servletRequestHandledEvent) {System.out.println("申请执行结束 -"+servletRequestHandledEvent.getRequestUrl());
    }
}

当一个申请执行结束时,该事件就会被触发。

3. 小结

这篇文章次要和小伙伴们分享了 SpringMVC 中 DispatcherServlet 的父类 FrameworkServlet,FrameworkServlet 的性能其实比较简单,次要就是在 service 办法中减少了对 PATCH 的解决,而后其余类型的申请都被归类到 processRequest 办法中进行对立解决,processRequest 办法则又分了三局部,首先是对 LocaleContext 和 RequestAttributes 的解决,而后执行 doService,最初在 finally 代码块中对 LocaleContext 和 RequestAttributes 属性进行还原,同时公布一个申请完结的事件。

doService 是重头戏,松哥将在下篇文章中和大家分享。好啦,明天就先和小伙伴们聊这么多~

正文完
 0