暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Tomcat如何实现Servlet规范(上)?

Alleria Windrunner 2021-09-07
212
我们知道,Servlet 容器最重要的任务就是创建 Servlet 的实例并且调用 Servlet,在前面两篇我们一起看了 Tomcat 如何定义自己的类加载器来加载 Servlet,但加载 Servlet 的类不等于创建 Servlet 的实例,类加载只是第一步,类加载好了才能创建类的实例,也就是说 Tomcat 先加载 Servlet 的类,然后在 Java 堆上创建了一个 Servlet 实例。
一个 Web 应用里往往有多个 Servlet,而在 Tomcat 中一个 Web 应用对应一个 Context 容器,也就是说一个 Context 容器需要管理多个 Servlet 实例。但 Context 容器并不直接持有 Servlet 实例,而是通过子容器 Wrapper 来管理 Servlet,你可以把 Wrapper 容器看作是 Servlet 的包装。
那为什么需要 Wrapper 呢?Context 容器直接维护一个 Servlet 数组不就行了吗?这是因为 Servlet 不仅仅是一个类实例,它还有相关的配置信息,比如它的 URL 映射、它的初始化参数,因此设计出了一个包装器,把 Servlet 本身和它相关的数据包起来,没错,这就是面向对象的思想。
那管理好 Servlet 就完事大吉了吗?别忘了 Servlet 还有两个兄弟:Listener 和 Filter,它们也是 Servlet 规范中的重要成员,因此 Tomcat 也需要创建它们的实例,也需要在合适的时机去调用它们的方法。


Servlet 管理

前面提到,Tomcat 是用 Wrapper 容器来管理 Servlet 的,那 Wrapper 容器具体长什么样子呢?我们先来看看它里面有哪些关键的成员变量:
    protected volatile Servlet instance = null;
    复制
    毫无悬念,它拥有一个 Servlet 实例,并且 Wrapper 通过 loadServlet 方法来实例化 Servlet。为了方便你阅读,我简化了代码:
      public synchronized Servlet loadServlet() throws ServletException {
      Servlet servlet;

      //1. 创建一个Servlet实例
      servlet = (Servlet) instanceManager.newInstance(servletClass);

      //2.调用了Servlet的init方法,这是Servlet规范要求的
      initServlet(servlet);

      return servlet;
      }
      复制
      其实 loadServlet 主要做了两件事:创建 Servlet 的实例,并且调用 Servlet 的 init 方法,因为这是 Servlet 规范要求的。
      那接下来的问题是,什么时候会调到这个 loadServlet 方法呢?为了加快系统的启动速度,我们往往会采取资源延迟加载的策略,Tomcat 也不例外,默认情况下 Tomcat 在启动时不会加载你的 Servlet,除非你把 Servlet 的loadOnStartup参数设置为true。
      这里还需要你注意的是,虽然 Tomcat 在启动时不会创建 Servlet 实例,但是会创建 Wrapper 容器,就好比尽管枪里面还没有子弹,先把枪造出来。那子弹什么时候造呢?是真正需要开枪的时候,也就是说有请求来访问某个 Servlet 时,这个 Servlet 的实例才会被创建。
      那 Servlet 是被谁调用的呢?我们回忆一下前面提到过 Tomcat 的 Pipeline-Valve 机制,每个容器组件都有自己的 Pipeline,每个 Pipeline 中有一个 Valve 链,并且每个容器组件有一个 BasicValve(基础阀)。Wrapper 作为一个容器组件,它也有自己的 Pipeline 和 BasicValve,Wrapper 的 BasicValve 叫 StandardWrapperValve。
      你可以想到,当请求到来时,Context 容器的 BasicValve 会调用 Wrapper 容器中 Pipeline 中的第一个 Valve,然后会调用到 StandardWrapperValve。我们先来看看它的 invoke 方法是如何实现的,同样为了方便你阅读,我简化了代码:
        public final void invoke(Request request, Response response) {


        //1.实例化Servlet
        servlet = wrapper.allocate();

        //2.给当前请求创建一个Filter链
        ApplicationFilterChain filterChain =
        ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);


        //3. 调用这个Filter链,Filter链中的最后一个Filter会调用Servlet
        filterChain.doFilter(request.getRequest(), response.getResponse());


        }
        复制
        StandardWrapperValve 的 invoke 方法比较复杂,去掉其他异常处理的一些细节,本质上就是三步:
        • 第一步,创建 Servlet 实例;

        • 第二步,给当前请求创建一个 Filter 链;

        • 第三步,调用这个 Filter 链。


        你可能会问,为什么需要给每个请求创建一个 Filter 链?这是因为每个请求的请求路径都不一样,而 Filter 都有相应的路径映射,因此不是所有的 Filter 都需要来处理当前的请求,我们需要根据请求的路径来选择特定的一些 Filter 来处理。
        第二个问题是,为什么没有看到调到 Servlet 的 service 方法?这是因为 Filter 链的 doFilter 方法会负责调用 Servlet,具体来说就是 Filter 链中的最后一个 Filter 会负责调用 Servlet。

        文章转载自Alleria Windrunner,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

        评论