4.1 四层容器与请求路由 本节摘要:容器是 Tomcat 的指路系统。本节拆解 Engine、Host、Context、Wrapper 四层的嵌套关系与各自的路由依据(主机名、上下文路径、Servlet 映射),讲清 Mapper 的匹配顺序与通配规则,并完成多虚拟主机的配置与验证。学完你面对任何一个 URL,都能逐段说出它被谁消费、最终落到哪个 Servlet 手里。 进城之后的第一段。3.3 结尾处请求刚被适配器翻译成 Servlet 规范对象,本节就看它如何在这座四层迷宫里被一层层筛到唯一的处理者——这也是理解"应用隔离""多域名部署""路径 404"的地基。 请求进了门,谁来指路?
本节摘要:容器是 Tomcat 的指路系统。本节拆解 Engine、Host、Context、Wrapper 四层的嵌套关系与各自的路由依据(主机名、上下文路径、Servlet 映射),讲清 Mapper 的匹配顺序与通配规则,并完成多虚拟主机的配置与验证。学完你面对任何一个 URL,都能逐段说出它被谁消费、最终落到哪个 Servlet 手里。
进城之后的第一段。3.3 结尾处请求刚被适配器翻译成 Servlet 规范对象,本节就看它如何在这座四层迷宫里被一层层筛到唯一的处理者——这也是理解"应用隔离""多域名部署""路径 404"的地基。
一个 URL 拆开是三段情报:主机名(Host 头)、应用路径(URI 第一段)、资源路径(URI 其余部分)。四层容器各自消费一段:Engine 拿主机名选 Host,Host 拿应用路径选 Context,Context 拿资源路径按映射规则选 Wrapper,Wrapper 里住着那个 Servlet。三次筛选,层层收窄,这就是"迷宫"的全部秘密——它不是真的迷,是精确的分诊。

嵌套不只是逻辑游戏,它决定了三件实事:配置继承(Host 级阀门对它所有 Context 生效)、类隔离(每个 Context 独立类加载器,两个应用各带同名的库互不冲突)、生命周期级联(停一个 Host 等于停它下面全部应用)。第 5 章讲阀门挂点时,全靠这张嵌套图定位。
真正做筛选的是 Mapper 组件,它的匹配顺序有严格优先级,很多"怎么也猜不到哪个 Servlet 被调用"的问题出在这里。
第一步选 Host:拿请求的 Host 头精确匹配各虚拟主机名,全不中则落到 Engine 的 defaultHost。第二步选 Context:拿 URI 第一段与各上下文路径做最长前缀匹配——注意是最长优先,同时部署了路径为根与路径为应用一的两个 Context 时,带前缀的请求先被更长的匹配吃掉。第三步选 Wrapper,四条规则从高到低:精确字符串匹配最优;其次前缀通配(斜杠加星形式的路径模式);再次扩展名通配(星加点形式);最后落到默认 Servlet,它负责静态文件与目录列表,也是 404 的最终宣判者。
一段配置胜过十句解释。下面这个最小应用的路由结果可以逐条推演。
<!-- 应用内 web.xml:同一个请求路径三种映射规则的较量 --> <servlet> <servlet-name>exact</servlet-name> <servlet-class>demo.OrderListServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>exact</servlet-name> <url-pattern>/order/list</url-pattern> <!-- 精确匹配 --> </servlet-mapping> <servlet-mapping> <servlet-name>exact</servlet-name> <url-pattern>/order/*</url-pattern> <!-- 前缀通配 --> </servlet-mapping> <servlet-mapping> <servlet-name>exact</servlet-name> <url-pattern>*.do</url-pattern> <!-- 扩展名通配 --> </servlet-mapping>
推演三个请求:路径完全等于订单列表路径的,走精确匹配;路径为订单前缀加任意子路径的,走前缀通配;任何以 do 结尾的,走扩展名通配。如果三个规则同用(如上),精确的那条永远赢—— Mapper 按优先级排序后取第一个命中。理解这套优先级,"为什么我的请求进了错误的 Servlet"就有了推理路径。
多域名分流是四层结构最直观的应用。给商城域名与默认主机各配一个 Host,各指向自己的应用目录。
<!-- server.xml:双虚拟主机 --> <Engine name="Catalina" defaultHost="localhost"> <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"/> <Host name="mall.example.com" appBase="mall-apps" unpackWARs="true" autoDeploy="true"> <Alias>www.mall.example.com</Alias> <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="mall_access" suffix=".txt" pattern="%h %t "%r" %s %b"/> </Host> </Engine>
验证不用真解析域名,curl 可以直接携带 Host 头模拟。
# 同一实例 两种身份访问 对比落点 curl -s -H 'Host: mall.example.com' http://localhost:8080/ | head -n 3 curl -s -H 'Host: unknown.example.com' http://localhost:8080/ | head -n 3
<!-- mall-apps/ROOT 应用返回的页面 --> <!DOCTYPE html> <html>商城前台首页</html> <!-- 未知域名落回 defaultHost 返回默认欢迎页 --> <!DOCTYPE html> <html>Tomcat 默认页</html>
第一个请求进了商城主机,第二个因为没有任何 Host 匹配而落到 defaultHost 指定的 localhost——这个兜底行为很重要:配错域名不会报错,只会悄悄落错店,排查"访问内容不对"时先查 Host 头与 defaultHost。另外注意每个 Host 的 appBase 指向不同目录,同名应用在两台主机下互不干扰,访问日志也按主机分文件——嵌套结构带来的隔离在配置层面处处可见。
⚠️ 常见坑:给 Host 起的名字与实际访问域名大小写或尾部点号不一致,匹配失败落回默认主机,页面"看起来部署错了"。验证永远用带 Host 头的 curl,一次就能定位。
迷宫的路认得了,下一节看住户怎么搬进来——三种部署方式与那棵标准目录树。