Tomcat 系列篇十-介绍下 Tomcat 里的 Mapper 作用

Mapper 顾名思义是作一个映射作用,在 Tomcat 中会根据域名找到 host 组件,再根据 uri 可以找到对应的 context 和 wrapper 组件,但是对于当前这个环境 (Springboot) 会有一点小区别
之前说到
请求会经过 coyote 适配器进行进一步处理,
org.apache.coyote.http11.Http11Processor#service

1
2
3
4
5
// Process the request in the adapter
if (getErrorState().isIoAllowed()) {
try {
rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
getAdapter().service(request, response);

然后到 coyoteAdapter 的 service
org.apache.catalina.connector.CoyoteAdapter#service

1
2
3
4
5
6
7
8
9
10
11
12
13
try {
// Parse and set Catalina and configuration specific
// request parameters
postParseSuccess = postParseRequest(req, request, res, response);
if (postParseSuccess) {
//check valves if we support async
request.setAsyncSupported(
connector.getService().getContainer().getPipeline().isAsyncSupported());
// Calling the container
// ----------> 到这就是调用 pipeline 去处理了,我们要关注上面的 postParseRequest
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
}

主要先看到 postParseRequest
在 postParseRequest 的代码里会调用 Mapper 的 map 方法

1
2
3
4
5
6
7
8
9
10
while (mapRequired) {
// This will map the the latest version by default
connector.getService().getMapper().map(serverName, decodedURI,
version, request.getMappingData());

// If there is no context at this point, either this is a 404
// because no ROOT context has been deployed or the URI was invalid
// so no context could be mapped.
if (request.getContext() == null) {
// Allow processing to continue.

而后面的 context 就是在 map 方法里处理塞进去的
往里看就是 org.apache.catalina.mapper.Mapper#internalMap
第一步找的是 host

1
2
3
// Virtual host mapping
MappedHost[] hosts = this.hosts;
MappedHost mappedHost = exactFindIgnoreCase(hosts, host);

而在后续代码里继续设置 context

1
2
3
4
5
6
if (contextVersion == null) {
// Return the latest version
// The versions array is known to contain at least one element
contextVersion = contextVersions[versionCount - 1];
}
mappingData.context = contextVersion.object;

然后是 wrapper

1
2
3
4
// Wrapper mapping
if (!contextVersion.isPaused()) {
internalMapWrapper(contextVersion, uri, mappingData);
}

在这个方法 org.apache.catalina.mapper.Mapper#internalMapWrapper

1
2
3
4
5
6
7
8
9
// Rule 7 -- Default servlet
if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
if (contextVersion.defaultWrapper != null) {
mappingData.wrapper = contextVersion.defaultWrapper.object;
mappingData.requestPath.setChars
(path.getBuffer(), path.getStart(), path.getLength());
mappingData.wrapperPath.setChars
(path.getBuffer(), path.getStart(), path.getLength());
mappingData.matchType = MappingMatch.DEFAULT;

这里就设置了 wrapper ,因为我们是在 Springboot 中,所以只有默认的 dispatchServlet
上面主要是在请求处理过程中的查找映射过程,一开始的注册是从 MapperListener 开始的
MapperListener 继承了 LifecycleMbeanBase,也就是有了 Lifecycle 状态变化那一套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@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);
}
}
}

在启动过程中就会去把 engine 的子容器 host 找出来进行注册,就是调用 registerHost 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void registerHost(Host host) {

String[] aliases = host.findAliases();
mapper.addHost(host.getName(), aliases, host);

for (Container container : host.findChildren()) {
if (container.getState().isAvailable()) {
registerContext((Context) container);
}
}

// Default host may have changed
findDefaultHost();

if(log.isDebugEnabled()) {
log.debug(sm.getString("mapperListener.registerHost",
host.getName(), domain, service));
}
}

在这里面会添加 host 组件,注册 context 等,注册context 里还会处理 wrapper 的添加记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private void registerContext(Context context) {

String contextPath = context.getPath();
if ("/".equals(contextPath)) {
contextPath = "";
}
Host host = (Host)context.getParent();

WebResourceRoot resources = context.getResources();
String[] welcomeFiles = context.findWelcomeFiles();
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));
}
}

mapper.addContextVersion(host.getName(), host, contextPath,
context.getWebappVersion(), context, welcomeFiles, resources,
wrappers);

if(log.isDebugEnabled()) {
log.debug(sm.getString("mapperListener.registerContext",
contextPath, service));
}
}

就是在 org.apache.catalina.mapper.Mapper#addContextVersion 方法中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public void addContextVersion(String hostName, Host host, String path,
String version, Context context, String[] welcomeResources,
WebResourceRoot resources, Collection<WrapperMappingInfo> wrappers) {

hostName = renameWildcardHost(hostName);

MappedHost mappedHost = exactFind(hosts, hostName);
if (mappedHost == null) {
addHost(hostName, new String[0], host);
mappedHost = exactFind(hosts, hostName);
if (mappedHost == null) {
log.error(sm.getString("mapper.addContext.noHost", hostName));
return;
}
}
if (mappedHost.isAlias()) {
log.error(sm.getString("mapper.addContext.hostIsAlias", hostName));
return;
}
int slashCount = slashCount(path);
synchronized (mappedHost) {
ContextVersion newContextVersion = new ContextVersion(version,
path, slashCount, context, resources, welcomeResources);
if (wrappers != null) {
addWrappers(newContextVersion, wrappers);
}

大致介绍了下 Mapper 的逻辑