Tomcat 是怎么找到用来处理请求的 Servlet 的?
原文地址:https://alphahinex.github.io/2020/08/02/how-request-find-servlet/
coverdescription: "很多事来不及思考,就这样自然发生了"
date: 2020.08.02 10:34
categories:
- Tomcat
tags: [Java, Tomcat, Servlet]
keywords: Java, Tomcat, Servlet, Mapper, MapperListener, Service, Engine, Host, Context, Wrapper, ContextVersion
在 Servlet 注册到了哪? 中,我们找到了配置的 Servlet 被包装成了一个 StandardWrapper
,以注册的 Servlet name 为 key 放入了其父容器(Context)一个 HashMap 里。那么当 Tomcat 收到一个请求的时候,是怎么找到对应的 Servlet 以对请求进行处理的呢?
先放一张图:
Tomcat Server总的来说,这个过程分为两部分:
- 读取所有 Servlet 的配置,放入 Mapper 中;
- 将请求匹配到具体的 Servlet 上。
注:本文以 Tomcat v9.0.35 版本源码为例进行说明。
读取所有 Servlet 的配置,放入 Mapper 中
在 Tomcat Server 启动时,会将其所包含的所有 Service 也一同启动。
StandardServer.java#L927-L932:
// Start our defined Services
synchronized (servicesLock) {
for (Service service : services) {
service.start();
}
}
每个 Service 对应一个 Mapper,Mapper 是本文的主角,包含了 Servlet 的映射信息。在 Service 启动时,会同时启动一个 mapperListener。
mapperListener.start();
MapperListener 可以 通过 Service 来构造,里面包含了这个 Service 以及 Service 中的 Mapper。
MapperListener 在 start 时,会获取 Service 对应的 Engine,并将 Engine 中的所有 Host 进行注册。
@Override
public void startInternal() throws LifecycleException {
setState(LifecycleState.STARTING);
Engine engine = service.getContainer();
if (engine == null) {
return;
}
findDefaultHost();
addListeners(engine);
Container[] conHosts = engine.findChildren();
for (Container conHost : conHosts) {
Host host = (Host) conHost;
if (!LifecycleState.NEW.equals(host.getState())) {
// Registering the host will register the context and wrappers
registerHost(host);
}
}
}
registerHost 中会注册其中的 Context。
MapperListener.java#L305-L309:
for (Container container : host.findChildren()) {
if (container.getState().isAvailable()) {
registerContext((Context) container);
}
}
registerContext 时,将 Context 中的 Wrapper(包含一个 Servlet) 包装成 WrapperMappingInfo,
MapperListener.java#L381-L390:
List<WrapperMappingInfo> wrappers = new ArrayList<>();
for (Container container : context.findChildren()) {
prepareWrapperMappingInfo(context, (Wrapper) container, wrappers);
if(log.isDebugEnabled()) {
log.debug(sm.getString("mapperListener.registerWrapper",
container.getName(), contextPath, service));
}
}
并进行了如下操作。
MapperListener.java#L392-L394:
mapper.addContextVersion(host.getName(), host, contextPath,
context.getWebappVersion(), context, welcomeFiles, resources,
wrappers);
addContextVersion 方法根据 Context 创建了一个 ContextVersion 对象,并通过 addWrappers 及 addWrapper 方法将 WrapperMappingInfo 中的 Wrapper 及相关信息,添加进 ContextVersion 里。
ContextVersion 是 Mapper 中定义的内部类,包含了如下属性:
public MappedWrapper defaultWrapper = null;
public MappedWrapper[] exactWrappers = new MappedWrapper[0];
public MappedWrapper[] wildcardWrappers = new MappedWrapper[0];
public MappedWrapper[] extensionWrappers = new MappedWrapper[0];
分别对应按 如下方式 匹配的 Servlet:
prop | url mapping |
---|---|
defaultWrapper | / |
exactWrappers | 完全匹配 |
wildcardWrappers | 以 /* 结尾 |
extensionWrappers | 以 *. 开头 |
addContextVersion
方法将构造好的 ContextVersion 对象,放入 contextObjectToContextVersionMap 中备用。
contextObjectToContextVersionMap.put(context, newContextVersion);
/**
* Mapping from Context object to Context version to support
* RequestDispatcher mappings.
*/
private final Map<Context, ContextVersion> contextObjectToContextVersionMap =
new ConcurrentHashMap<>();
并同时将 ContextVersion 放入 Mapper 里的 MappedHost[] hosts 的 ContextList contextList 的 MappedContext[] contexts 里。
ContextList contextList = mappedHost.contextList;
MappedContext mappedContext = exactFind(contextList.contexts, path);
if (mappedContext == null) {
mappedContext = new MappedContext(path, newContextVersion);
ContextList newContextList = contextList.addContext(
mappedContext, slashCount);
if (newContextList != null) {
updateContextList(mappedHost, newContextList);
contextObjectToContextVersionMap.put(context, newContextVersion);
}
} else {
ContextVersion[] contextVersions = mappedContext.versions;
ContextVersion[] newContextVersions = new ContextVersion[contextVersions.length + 1];
if (insertMap(contextVersions, newContextVersions,
newContextVersion)) {
mappedContext.versions = newContextVersions;
contextObjectToContextVersionMap.put(context, newContextVersion);
} else {
// Re-registration after Context.reload()
// Replace ContextVersion with the new one
int pos = find(contextVersions, version);
if (pos >= 0 && contextVersions[pos].name.equals(version)) {
contextVersions[pos] = newContextVersion;
contextObjectToContextVersionMap.put(context, newContextVersion);
}
}
}
至此,所有根据请求匹配到具体 Servlet 的准备工作都已完成。
将请求匹配到具体的 Servlet 上
先放一张官网的 图片
Apache Tomcat 9 Architecture Request Process Flow链接虽然是在 Tomcat 9.0 的文档中,但图片中的内容有些已经过时了,比如 Http11Protocol 在 9.0 中已经移除了。
对上面流程简单分下组:
Apache Tomcat 9 Architecture Request Process Flow with group可以看到,整个流程涉及到的类大致分布在 tomcat
、coyote
和 catalina
三个包下。
在 tomcat 9.0.35 版本中,org.apache 包下面共有 七个包:
├── catalina
├── coyote
├── el
├── jasper
├── juli
├── naming
└── tomcat
org.apache.tomcat
包下为网络、线程、连接池、WebSocket 及一些工具类。其余各包作用可见下图:
CoyoteAdapter 的 postParseRequest 方法中,根据 connector 找到了其对应的 Service,进而找到 Service 对应的 Mapper,并进行了 map 操作。
// This will map the the latest version by default
connector.getService().getMapper().map(serverName, decodedURI,
version, request.getMappingData());
map 方法根据请求找到对应的 MappedHost 的 MappedConext,进而找到之前放入的 ContextVersion,详细过程可见 Mapper 中的 internalMap 方法。
在 internalMap
方法中,会调用 internalMapWrapper 方法进行 Wrapper 的 Mapping,会根据不同的规则,匹配 ContextVersion 中不同的 MappedWrapper,并将匹配到的 Wrapper 放入 request 的 MappingData 中。比如 internalMapWildcardWrapper 里 1134 行:
mappingData.wrapper = wrappers[pos].object;
随后在 StandardContextValve 的 invoke 方法中,便可通过 request.getWrapper() 获取到请求对应的 Wrapper。之后的流程与上面时序图中的过程就基本一致了。
参考资料
附录
附两张手稿
初始化匹配