🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
### 请求定位Servlet的过程 一个请求是如何定位到让哪个`Wrapper`的`Servlet`处理的?答案是,Tomcat 是用 Mapper 组件来完成这个任务的。 `Mapper`组件的功能就是将用户请求的`URL`定位到一个`Servlet`,它的工作原理是:`Mapper`组件里保存了 Web 应用的配置信息,其实就是**容器组件与访问路径的映射关系**,比如`Host`容器里配置的域名、`Context`容器里的`Web`应用路径,以及`Wrapper`容器里`Servlet`映射的路径,你可以想象这些配置信息就是一个多层次的`Map`。 当一个请求到来时,`Mapper`组件通过解析请求 URL 里的域名和路径,再到自己保存的 Map 里去查找,就能定位到一个`Servlet`。请你注意,一个请求 URL 最后只会定位到一个`Wrapper`容器,也就是一个`Servlet` ![](https://img.kancloud.cn/87/bb/87bbe3aa7d90ff2ec69fd090f22c3c01_1564x1140.png) 假如有用户访问一个 URL,比如图中的`http://user.shopping.com:8080/order/buy`,Tomcat 如何将这个 URL 定位到一个 Servlet 呢? 1. **首先根据协议和端口号确定 Service 和 Engine**。Tomcat 默认的 HTTP 连接器监听 8080 端口、默认的 AJP 连接器监听 8009 端口。上面例子中的 URL 访问的是 8080 端口,因此这个请求会被 HTTP 连接器接收,而一个连接器是属于一个 Service 组件的,这样 Service 组件就确定了。我们还知道一个 Service 组件里除了有多个连接器,还有一个容器组件,具体来说就是一个 Engine 容器,因此 Service 确定了也就意味着 Engine 也确定了。 2. **根据域名选定 Host。**Service 和 Engine 确定后,Mapper 组件通过 URL 中的域名去查找相应的 Host 容器,比如例子中的 URL 访问的域名是`user.shopping.com`,因此 Mapper 会找到 Host2 这个容器。 3. **根据 URL 路径找到 Context 组件。**Host 确定以后,Mapper 根据 URL 的路径来匹配相应的 Web 应用的路径,比如例子中访问的是 /order,因此找到了 Context4 这个 Context 容器。 4. **根据 URL 路径找到 Wrapper(Servlet)。**Context 确定后,Mapper 再根据 web.xml 中配置的 Servlet 映射路径来找到具体的 Wrapper 和 Servlet 连接器中的 Adapter 会调用容器的 Service 方法来执行 Servlet,最先拿到请求的是 Engine 容器,Engine 容器对请求做一些处理后,会把请求传给自己子容器 Host 继续处理,依次类推,最后这个请求会传给 Wrapper 容器,Wrapper 会调用最终的 Servlet 来处理。那么这个调用过程具体是怎么实现的呢?答案是使用 Pipeline-Valve 管道。 `Pipeline-Valve`是责任链模式,责任链模式是指在一个请求处理的过程中有很多处理者依次对请求进行处理,每个处理者负责做自己相应的处理,处理完之后将再调用下一个处理者继续处理,Valve 表示一个处理点(也就是一个处理阀门),因此`invoke`方法就是来处理请求的。 ~~~ public interface Valve { public Valve getNext(); public void setNext(Valve valve); public void invoke(Request request, Response response) } ~~~ 继续看 Pipeline 接口 ~~~ public interface Pipeline { public void addValve(Valve valve); public Valve getBasic(); public void setBasic(Valve valve); public Valve getFirst(); } ~~~ `Pipeline`中有`addValve`方法。Pipeline 中维护了`Valve`链表,`Valve`可以插入到`Pipeline`中,对请求做某些处理。我们还发现 Pipeline 中没有 invoke 方法,因为整个调用链的触发是 Valve 来完成的,`Valve`完成自己的处理后,调用`getNext.invoke()`来触发下一个 Valve 调用。 其实每个容器都有一个 Pipeline 对象,只要触发了这个 Pipeline 的第一个 Valve,这个容器里`Pipeline`中的 Valve 就都会被调用到。但是,不同容器的 Pipeline 是怎么链式触发的呢,比如 Engine 中 Pipeline 需要调用下层容器 Host 中的 Pipeline。 这是因为`Pipeline`中还有个`getBasic`方法。这个`BasicValve`处于`Valve`链表的末端,它是`Pipeline`中必不可少的一个`Valve`,负责调用下层容器的 Pipeline 里的第一个 Valve ![](https://img.kancloud.cn/d5/2d/d52d76bfad67c7c1060aade58a126c79_1550x1160.png) 整个过程分是通过连接器中的`CoyoteAdapter`触发,它会调用 Engine 的第一个 Valve: ``` @Override public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) { // 省略其他代码 // Calling the container connector.getService().getContainer().getPipeline().getFirst().invoke( request, response); ... } ``` Wrapper 容器的最后一个 Valve 会创建一个 Filter 链,并调用`doFilter()`方法,最终会调到`Servlet`的`service`方法。 前面我们不是讲到了`Filter`,似乎也有相似的功能,那`Valve`和`Filter`有什么区别吗?它们的区别是: * `Valve`是`Tomcat`的私有机制,与 Tomcat 的基础架构`API`是紧耦合的。`Servlet API`是公有的标准,所有的 Web 容器包括 Jetty 都支持 Filter 机制。 * 另一个重要的区别是`Valve`工作在 Web 容器级别,拦截所有应用的请求;而`Servlet Filter`工作在应用级别,只能拦截某个`Web`应用的所有请求。如果想做整个`Web`容器的拦截器,必须通过`Valve`来实现