Servlet 容器最重要的任务就是创建 Servlet 的实例并且调用 Servlet。
前面讲了 Tomcat 如何定义自己的类加载器来加载 Servlet。类加载只是第一步,类加载好了才能创建类的实例。
Tomcat 先加载 Servlet 的类,然后在 JVM 堆上创建 Servlet 实例。

Tomcat 中一个 Web 应用对应一个 Context 容器,一个 Web 应用往往有多个 Servlet。因此一个 Context 容器管理多个 Servlet 实例。

但 Context 容器并不直接持有 Servlet 实例,而是通过子容器 Wrapper 来管理 Servlet,Wrapper 容器看作是 Servlet 的包装。

Servlet 还有两个兄弟:Listener 和 Filter,它们也是 Servlet 规范中的重要成员,因此 Tomcat 也需要创建它们的实例,也需要在合适的时机去调用它们的方法。

Tomcat 的功能需求

除了前面提到的 加载 Servlet,Tomcat 也需要创建 Servlet、Listener、Filter 实例,在合适的时机去调用这些实例的方法。

Servlet 管理

Wrapper 包装 Servlet

Tomcat 在启动时不会创建 Servlet 实例,但是会创建 Wrapper 容器。
就好比尽管枪里面还没有子弹,先把枪造出来。那子弹什么时候造呢?是真正需要开枪的时候,也就是说有请求来访问某个 Servlet 时,这个 Servlet 的实例才会被创建。###资源延迟加载 加快启动速度
vue 也有延迟加载,JVM 也有延迟初始化
https://router.vuejs.org/zh/guide/advanced/lazy-loading.html

Wrapper 容器拥有一个 Servlet 实例,并且通过 loadServlet 方法来实例化 Servlet。

Wrapper 的 Pipeline 和 BasicValve

Tomcat 的 Pipeline-Valve 机制:每个容器组件都有自己的 Pipeline,每个 Pipeline 中有一个 Valve 链,并且每个容器组件有一个 BasicValve(基础阀)。Wrapper 作为一个容器组件,它也有自己的 Pipeline 和 BasicValve,Wrapper 的 BasicValve 叫 StandardWrapperValve。

当请求到来时,Context 容器的 BasicValve 会调用 Wrapper 容器中 Pipeline 中的第一个 Valve,然后会调用到 StandardWrapperValve。

StandardWrapperValve 的核心

StandardWrapperValve 的 invoke 方法比较复杂,本质上就是三步:
第一步,创建 Servlet 实例;
第二步,给当前请求创建一个 Filter 链;
第三步,调用这个 Filter 链。

为什么需要给每个请求创建一个 Filter 链?
每个请求的请求路径都不一样,映射到不同的 Filter。需要根据请求的路径来选择特定的一些 Filter 来处理。

Servlet 的 service 方法何时调用?Filter 链中的最后一个 Filter 会在 doFilter 方法中负责调用 Servlet 的 service 方法。

Filter 管理

跟 Servlet 一样,Filter 也可以在web.xml文件里进行配置。不同的是,Filter 的作用域是整个 Web 应用,因此 Filter 的实例是在 Context 容器中进行管理的,Context 容器用 Map 集合来保存 Filter。

Filter 链中有一个 Servlet 实例,因为上面提到了,每个 Filter 链最后都会调到一个 Servlet。所有 Filter 都调到了,就调用 Servlet 的 service 方法。

Filter 链跟 Tomcat 的 Pipeline-Valve 本质都是###责任链模式。
Filter 链是一系列 Filter 发生链式调用。
Pipeline-Valve 的背景是 Engine、Host、Context、Wrapper 容器沿着父子关系,都会对请求做一些处理。

Listener 管理

Listener 的作用

监听 Context 容器内部发生的事件
第一类是生命状态的变化,比如 Context 容器启动和停止、Session 的创建和销毁。
第二类是属性的变化,比如 Context 容器某个属性值变了、Session 的某个属性值变了以及新的请求来了等。

Tomcat 如何管理 listener

对于 Listener 来说,我们可以定制自己的监听器来监听 Tomcat 内部发生的各种事件:包括 Web 应用级别的、Session 级别的和请求级别的。

Tomcat 中的 Context 容器统一维护了这些监听器,并负责触发。Context 容器将两类事件分开来管理,分别用不同的集合来存放不同类型事件的监听器。

如何自定义 listener

ServletContextListener 接口是一种留给用户的扩展机制。用户可以实现这个接口来定义自己的监听器,监听 Context 容器的启停事件。Spring 就是这么做的。

我们实现了 listener 接口,然后配置 web.xml 或者添加注解。对于Tomcat 来说,它需要读取配置文件,拿到监听器类的名字,实例化这些类,并且在合适的时机调用监听器的方法。

ServletContextListener 和 LifecycleListener

跟 Tomcat 自己的生命周期事件 LifecycleListener 是不同的。LifecycleListener 定义在生命周期管理组件中,由基类 LifecycleBase 统一管理。

问题:

listener、context的关系

listener 被 context 容器管理,为什么还可以监测到context 容器的启动、停止事件。

Context 容器的源代码

Context 组件通过自定义类加载器来加载 Web 应用,并实现了 Servlet 规范,直接跟 Web 应用打交道,是一个核心的容器组件。也因此我用了很重的篇幅去讲解它,也非常建议你花点时间阅读一下它的源码。

不同监听器的存储

Context 容器分别用了 CopyOnWriteArrayList 和对象数组来存储两种不同的监听器,为什么要这样设计,你可以思考一下背后的原因。

一般 web.xml 中声明的一个 Servlet 只对应一个实例,如果一个 Servlet 实现了 SingleThreadModel,就会实例多个Servlet。

这里的Servlet 好像还只是 一个业务对象,可以构成对象池。
和Spring的关系,###单例bean!!!