Tomcat的使用总结(四)
MapperListener和Mapper
为了能够快速地通过指定的uri找到对应的wrapper及servlet,tomcat开发人员设计出了两个组件:MapperListener
和Mapper
。
MapperListener
MapperListener
主要作用如下:
- 通过监听容器的
AFTER_START_EVENT
事件来对容器进行注册; - 通过监听容器的
BEFORE_STOP_EVENT
事件来完成对容器的取消注册。
而Mapper作为uri映射到容器的工具,扮演的角色就是一个映射组件。它会缓存所有容器信息(包括容器名称、容器本身、容器层级等等),同时提供映射规则,将一个uri按照映射规则映射到具体的Host、Context和Wrapper,并最终通过Wrapper找到逻辑处理单元Servlet。
入口
在org.apache.catalina.core.StandardService
类中,我们看到两个关键的字段--mapper
和mapperListenter
。其中mapperListener
依赖service对象
进行构造。
/**
* Mapper.
*/
protected final Mapper mapper = new Mapper();
/**
* Mapper listener.
*/
protected final MapperListener mapperListener = new MapperListener(this);
MapperListener的注册过程
MapperListener
的构造方法比较简单,仅仅将mapper和service存储到当前对象的相关属性中。
public MapperListener(Service service) {
this.service = service;
this.mapper = service.getMapper();
}
MapperListener
在构造完成之后,会调用其start()
方法,我们来看看主要做了哪些事情?
- engine容器不存在,则MapperListener也不需要启动
- 查找默认主机,并设置到mapper的defaultHostName属性中
- 对容器及下面的所有子容器添加事件监听器
- 注册engine下面的host、context和wrapper,registerHost会注册host及下面的子容器
public void startInternal() throws LifecycleException {
setState(LifecycleState.STARTING);
// 1. engine容器不存在,则MapperListener也不需要启动
Engine engine = service.getContainer();
if (engine == null) {
return;
}
// 2. 查找默认主机,并设置到mapper的defaultHostName属性中
findDefaultHost();
// 3. 对容器及下面的所有子容器添加事件监听器
addListeners(engine);
// 4. 注册engine下面的host、context和wrapper,registerHost会注册host及下面的子容器
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);
}
}
}
接着我们看看方法findDefaultHost
,主要目的是检查并设置mapper
的defaultHostName
属性。
private void findDefaultHost() {
// 获取engine下面配置的defaultHost属性
Engine engine = service.getContainer();
String defaultHost = engine.getDefaultHost();
boolean found = false;
// 如果defaultHost属性不为空,则查找hosts下面的所有主机名及别名。
// 1. 找到了则设置到mapper的defaultHostName属性
// 2. 没找到则记录警告信息
if (defaultHost != null && defaultHost.length() >0) {
Container[] containers = engine.findChildren();
for (Container container : containers) {
Host host = (Host) container;
if (defaultHost.equalsIgnoreCase(host.getName())) {
found = true;
break;
}
String[] aliases = host.findAliases();
for (String alias : aliases) {
if (defaultHost.equalsIgnoreCase(alias)) {
found = true;
break;
}
}
}
}
if(found) {
mapper.setDefaultHostName(defaultHost);
} else {
log.warn(sm.getString("mapperListener.unknownDefaultHost",
defaultHost, service));
}
}
接下来我们分析addListeners
,该方法用于对所有容器设置监听器
。它是一个递归方法!
private void addListeners(Container container) {
// 对当前容器添加容器监听器和生命周期监听器,也就是当前对象
container.addContainerListener(this);
container.addLifecycleListener(this);
// 对当前容器下的子容器执行addListeners操作
for (Container child : container.findChildren()) {
addListeners(child);
}
}
接下来我们分析registerHost
,该方法用于往mapper
中注册虚拟主机
/**
* 注册虚拟主机
* Register host.
*/
private void registerHost(Host host) {
String[] aliases = host.findAliases();
// 往mapper中添加主机
mapper.addHost(host.getName(), aliases, host);
// 注册host下的每个context
for (Container container : host.findChildren()) {
if (container.getState().isAvailable()) {
registerContext((Context) container);
}
}
if(log.isDebugEnabled()) {
log.debug(sm.getString("mapperListener.registerHost",
host.getName(), domain, service));
}
}
registerHost
除了注册虚拟主机,额外会调用registerContext
来注册context
。我们看看这个方法。该方法完成了以下操作:
- contextPath如果为斜杠,则统一转换为空字符串
- 将context下面的每个wrapper都添加到mapper
- 将context添加到mapper
/**
* 注册context
* Register context.
*/
private void registerContext(Context context) {
// contextPath如果为斜杠,则统一转换为空字符串
String contextPath = context.getPath();
if ("/".equals(contextPath)) {
contextPath = "";
}
Host host = (Host)context.getParent();
WebResourceRoot resources = context.getResources();
String[] welcomeFiles = context.findWelcomeFiles();
List wrappers = new ArrayList();
// 将context下面的每个wrapper都添加到mapper
for (Container container : context.findChildren()) {
// 准备wrapper信息,以便后续插入mapper
prepareWrapperMappingInfo(context, (Wrapper) container, wrappers);
if(log.isDebugEnabled()) {
log.debug(sm.getString("mapperListener.registerWrapper",
container.getName(), contextPath, service));
}
}
// 将context添加到mapper
mapper.addContextVersion(host.getName(), host, contextPath,
context.getWebappVersion(), context, welcomeFiles, resources,
wrappers);
if(log.isDebugEnabled()) {
log.debug(sm.getString("mapperListener.registerContext",
contextPath, service));
}
}
关键方法为prepareWrapperMappingInfo
,用于准备注册到mapper下的wrapper,这儿mapper对于wrapper的支持是wrapper的包装对象--WrapperMappingInfo
。而一个context可能有多个wrapper,所以WrapperMappingInfo
是一个list
。我们来分析一下这个list对象的生成方法--prepareWrapperMappingInfo
。
该方法就是将映射url
、wrapper名字
和资源只读标记
等信息组合成对象添加到wrappers中。
private void prepareWrapperMappingInfo(Context context, Wrapper wrapper,
List wrappers) {
String wrapperName = wrapper.getName();
boolean resourceOnly = context.isResourceOnlyServlet(wrapperName);
String[] mappings = wrapper.findMappings();
for (String mapping : mappings) {
boolean jspWildCard = (wrapperName.equals("jsp")
&& mapping.endsWith("/*"));
wrappers.add(new WrapperMappingInfo(mapping, wrapper, jspWildCard,
resourceOnly));
}
}
MapperListener的取消注册过程
在tomcat组件中,start()
的逆向过程为stop()
。MapperListener组件
也不例外。我们来分析一下其stop()
方法,该方法的作用是将当前监听器从容器中移除。
@Override
public void stopInternal() throws LifecycleException {
setState(LifecycleState.STOPPING);
Engine engine = service.getContainer();
if (engine == null) {
return;
}
removeListeners(engine);
}
private void removeListeners(Container container) {
container.removeContainerListener(this);
container.removeLifecycleListener(this);
for (Container child : container.findChildren()) {
removeListeners(child);
}
}
当每个容器在stop()
方法被调用的时候,都会触发相应的容器事件。我们看看ContainerBase
下面触发事件的代码,该方法会调用所有容器监听器的containerEvent()
方法。
@Override
public void fireContainerEvent(String type, Object data) {
if (listeners.size() < 1)
return;
ContainerEvent event = new ContainerEvent(this, type, data);
// Note for each uses an iterator internally so this is safe
for (ContainerListener listener : listeners) {
listener.containerEvent(event);
}
}
当每个容器在stop()
方法被调用的时候,都会触发相应的生命周期事件,我们看看LifecycleBase
下面触发事件的代码,就是调用生命周期监听器的lifecycleEvent()
方法
protected void fireLifecycleEvent(String type, Object data) {
LifecycleEvent event = new LifecycleEvent(this, type, data);
for (LifecycleListener listener : lifecycleListeners) {
listener.lifecycleEvent(event);
}
}
好了我们已经看到了MapperListener接下来要分析的方法了,即:containerEvent()
容器方法和lifecycleEvent()
生命周期方法。
先来看containerEvent()
,虽然该方法的代码非常得长,但是逻辑却很简单。该方法有非常多的if-else
(虽然我们推荐使用设计模式代替)。所有的操作都是对mapper缓存的资源进行增删改操作。
@Override
public void containerEvent(ContainerEvent event) {
if (Container.ADD_CHILD_EVENT.equals(event.getType())) {
Container child = (Container) event.getData();
addListeners(child);
// If child is started then it is too late for life-cycle listener
// to register the child so register it here
if (child.getState().isAvailable()) {
if (child instanceof Host) {
registerHost((Host) child);
} else if (child instanceof Context) {
registerContext((Context) child);
} else if (child instanceof Wrapper) {
// Only if the Context has started. If it has not, then it
// will have its own "after_start" life-cycle event later.
if (child.getParent().getState().isAvailable()) {
registerWrapper((Wrapper) child);
}
}
}
} else if (Container.REMOVE_CHILD_EVENT.equals(event.getType())) {
Container child = (Container) event.getData();
removeListeners(child);
// No need to unregister - life-cycle listener will handle this when
// the child stops
} else if (Host.ADD_ALIAS_EVENT.equals(event.getType())) {
// Handle dynamically adding host aliases
mapper.addHostAlias(((Host) event.getSource()).getName(),
event.getData().toString());
} else if (Host.REMOVE_ALIAS_EVENT.equals(event.getType())) {
// Handle dynamically removing host aliases
mapper.removeHostAlias(event.getData().toString());
} else if (Wrapper.ADD_MAPPING_EVENT.equals(event.getType())) {
// Handle dynamically adding wrappers
Wrapper wrapper = (Wrapper) event.getSource();
Context context = (Context) wrapper.getParent();
String contextPath = context.getPath();
if ("/".equals(contextPath)) {
contextPath = "";
}
String version = context.getWebappVersion();
String hostName = context.getParent().getName();
String wrapperName = wrapper.getName();
String mapping = (String) event.getData();
boolean jspWildCard = ("jsp".equals(wrapperName)
&& mapping.endsWith("/*"));
mapper.addWrapper(hostName, contextPath, version, mapping, wrapper,
jspWildCard, context.isResourceOnlyServlet(wrapperName));
} else if (Wrapper.REMOVE_MAPPING_EVENT.equals(event.getType())) {
// Handle dynamically removing wrappers
Wrapper wrapper = (Wrapper) event.getSource();
Context context = (Context) wrapper.getParent();
String contextPath = context.getPath();
if ("/".equals(contextPath)) {
contextPath = "";
}
String version = context.getWebappVersion();
String hostName = context.getParent().getName();
String mapping = (String) event.getData();
mapper.removeWrapper(hostName, contextPath, version, mapping);
} else if (Context.ADD_WELCOME_FILE_EVENT.equals(event.getType())) {
// Handle dynamically adding welcome files
Context context = (Context) event.getSource();
String hostName = context.getParent().getName();
String contextPath = context.getPath();
if ("/".equals(contextPath)) {
contextPath = "";
}
String welcomeFile = (String) event.getData();
mapper.addWelcomeFile(hostName, contextPath,
context.getWebappVersion(), welcomeFile);
} else if (Context.REMOVE_WELCOME_FILE_EVENT.equals(event.getType())) {
// Handle dynamically removing welcome files
Context context = (Context) event.getSource();
String hostName = context.getParent().getName();
String contextPath = context.getPath();
if ("/".equals(contextPath)) {
contextPath = "";
}
String welcomeFile = (String) event.getData();
mapper.removeWelcomeFile(hostName, contextPath,
context.getWebappVersion(), welcomeFile);
} else if (Context.CLEAR_WELCOME_FILES_EVENT.equals(event.getType())) {
// Handle dynamically clearing welcome files
Context context = (Context) event.getSource();
String hostName = context.getParent().getName();
String contextPath = context.getPath();
if ("/".equals(contextPath)) {
contextPath = "";
}
mapper.clearWelcomeFiles(hostName, contextPath,
context.getWebappVersion());
}
}
接下来看看生命周期方法,lifecycleEvent()
。
@Override
public void lifecycleEvent(LifecycleEvent event) {
if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
Object obj = event.getSource();
if (obj instanceof Wrapper) {
Wrapper w = (Wrapper) obj;
// Only if the Context has started. If it has not, then it will
// have its own "after_start" event later.
if (w.getParent().getState().isAvailable()) {
registerWrapper(w);
}
} else if (obj instanceof Context) {
Context c = (Context) obj;
// Only if the Host has started. If it has not, then it will
// have its own "after_start" event later.
if (c.getParent().getState().isAvailable()) {
registerContext(c);
}
} else if (obj instanceof Host) {
registerHost((Host) obj);
}
} else if (event.getType().equals(Lifecycle.BEFORE_STOP_EVENT)) {
Object obj = event.getSource();
if (obj instanceof Wrapper) {
unregisterWrapper((Wrapper) obj);
} else if (obj instanceof Context) {
unregisterContext((Context) obj);
} else if (obj instanceof Host) {
unregisterHost((Host) obj);
}
}
}
AFTER_START_EVENT
我们前面已经分析过了,因此接下来我们主要分析分析BEFORE_STOP_EVENT
。该事件可能完成对Host、Context和Wrapper的取消注册操作。我们分别来看看这3个方法。
/**
* Unregister host.
* 对host进行取消注册操作,根据hostname来remove
*/
private void unregisterHost(Host host) {
String hostname = host.getName();
mapper.removeHost(hostname);
if(log.isDebugEnabled()) {
log.debug(sm.getString("mapperListener.unregisterHost", hostname,
domain, service));
}
}
/**
* Unregister context.
* 对context取消注册
*/
private void unregisterContext(Context context) {
String contextPath = context.getPath();
if ("/".equals(contextPath)) {
contextPath = "";
}
String hostName = context.getParent().getName();
if (context.getPaused()) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("mapperListener.pauseContext",
contextPath, service));
}
// 暂停的context,不能从mapper中移除,只能在mapper暂停
mapper.pauseContextVersion(context, hostName, contextPath,
context.getWebappVersion());
} else {
if (log.isDebugEnabled()) {
log.debug(sm.getString("mapperListener.unregisterContext",
contextPath, service));
}
// 非暂停的context,需要从mapper中移除
mapper.removeContextVersion(context, hostName, contextPath,
context.getWebappVersion());
}
}
/**
* Unregister wrapper.
* 对wrapper取消注册
*/
private void unregisterWrapper(Wrapper wrapper) {
Context context = ((Context) wrapper.getParent());
String contextPath = context.getPath();
String wrapperName = wrapper.getName();
if ("/".equals(contextPath)) {
contextPath = "";
}
String version = context.getWebappVersion();
String hostName = context.getParent().getName();
String[] mappings = wrapper.findMappings();
// 一个wrapper可能有多个map地址,对每个地址都需要移除操作,所以这儿是一个循环
for (String mapping : mappings) {
mapper.removeWrapper(hostName, contextPath, version, mapping);
}
if(log.isDebugEnabled()) {
log.debug(sm.getString("mapperListener.unregisterWrapper",
wrapperName, contextPath, service));
}
}
总结下来,BEFORE_STOP_EVENT
在MapperListener里面有下面的功能:
- 对host进行取消注册操作,根据hostname来remove
- 对context取消注册,暂停的context,不能从mapper中移除,只能在mapper暂停
- 对context取消注册,非暂停的context,需要从mapper中移除
- 对wrapper取消注册,一个wrapper可能有多个map地址,对每个地址都需要移除操作,所以这儿是一个循环
容器在Mapper的表现形式
在Mapper中,所有容器都使用MapElement
来表示,不同的容器有不同的子类实现,我们来看看类继承层级。
MapElement类继承层级
我们从父类MapElement
开始分析,这是一个protected修饰的抽象类,包含name
和object
两个属性。其中object
是泛型类型。
protected abstract static class MapElement {
public final String name;
public final T object;
public MapElement(String name, T object) {
this.name = name;
this.object = object;
}
}
MappedWrapper
为了减少对MapElement
子类依赖的说明,我们从MappedWrapper
开始说明每个子类的用途。
在MappedWrapper
中,object
为Wrapper容器。额外多了jsp通配符标记
和是否资源标记
两个boolean属性。
protected static class MappedWrapper extends MapElement {
public final boolean jspWildCard;
public final boolean resourceOnly;
public MappedWrapper(String name, Wrapper wrapper, boolean jspWildCard,
boolean resourceOnly) {
super(name, wrapper);
this.jspWildCard = jspWildCard;
this.resourceOnly = resourceOnly;
}
}
Context
为什么需要两个MapElement
子类呢?
从类继承层级来看,Context
容器关于MapElement
有两个子类ContextVersion
和MappedContext
,为什么需要有两个呢?
这儿不重复造轮子,参考下面的博客,我们就能知道原因。
简单来说,tomcat从7.x版本开始,在同一个tomcat中运行存在一个应用的多个版本,方便用户进行应用的热升级
。
- 多个应用版本是通过session来分配转化的。
- 假如我们有应用
app
、app##1
,app##2
这3个版本,app
表示最老的版本、app##1
表示次老的版本,app##2
表示最新的版本。 - 在部署
app##1
之前创建的session,在app##1
部署之后,仍然会请求到app
,直到session终结。 - 在
app##1
之后,app##2
之前创建的session,会请求到app##1
。 - 而
app##1
和app##2
也是有类似的处理方式。
【总结】:对应上述的app
、app##1
、app##2
,tomcat中会用3个ContextVersion
对象来表示,而MappedContext
是对这3个ContextVersion
的数组封装。
ContextVersion
分析了ContextVersion
和MappedContext
的区别和联系,我们接着看看ContextVersion
,泛型类型为Context
protected static final class ContextVersion extends MapElement {
public final String path; // contextPath,上下文路径
public final int slashCount; // 上下文路径的斜杠数量
public final WebResourceRoot resources; // 根web资源
public String[] welcomeResources; // 欢迎资源列表
public MappedWrapper defaultWrapper = null; // 默认wrapper
public MappedWrapper[] exactWrappers = new MappedWrapper[0]; // 准确wrapper列表
public MappedWrapper[] wildcardWrappers = new MappedWrapper[0]; // 通配符wrapper列表
public MappedWrapper[] extensionWrappers = new MappedWrapper[0]; // 扩展wrapper列表
public int nesting = 0; // wrapper嵌套层次,用于表示context下wrapper列表中最大的斜杠数
private volatile boolean paused; // 暂停标记
public ContextVersion(String version, String path, int slashCount,
Context context, WebResourceRoot resources,
String[] welcomeResources) {
super(version, context);
this.path = path;
this.slashCount = slashCount;
this.resources = resources;
this.welcomeResources = welcomeResources;
}
public boolean isPaused() {
return paused;
}
public void markPaused() {
paused = true;
}
}
MappedContext
一个MappedContext
是ContextVersion[]
数组的包装。
protected static final class MappedContext extends MapElement {
public volatile ContextVersion[] versions;
public MappedContext(String name, ContextVersion firstVersion) {
super(name, null);
this.versions = new ContextVersion[] { firstVersion };
}
}
ContextList
ContextList
是MappedContext[]
数组的封装。同时提供对MappedContext
的新增和删除操作。
protected static final class ContextList {
public final MappedContext[] contexts;
public final int nesting;
public ContextList() {
this(new MappedContext[0], 0);
}
private ContextList(MappedContext[] contexts, int nesting) {
this.contexts = contexts;
this.nesting = nesting;
}
public ContextList addContext(MappedContext mappedContext,
int slashCount) {
MappedContext[] newContexts = new MappedContext[contexts.length + 1];
if (insertMap(contexts, newContexts, mappedContext)) {
return new ContextList(newContexts, Math.max(nesting,
slashCount));
}
return null;
}
public ContextList removeContext(String path) {
MappedContext[] newContexts = new MappedContext[contexts.length - 1];
if (removeMap(contexts, newContexts, path)) {
int newNesting = 0;
for (MappedContext context : newContexts) {
newNesting = Math.max(newNesting, slashCount(context.name));
}
return new ContextList(newContexts, newNesting);
}
return null;
}
}
MappedHost
该方法有一些重要的属性和特征。包括:
- ContextList contextList,上下文列表
- MappedHost realHost
- 真实的host,一个主机可能有多个别名。
- 所有
别名的MappedHost
共享一个非别名的MappedHost
,并存放在这个属性中
- List aliases
别名MappedHost列表
。- 为了统一处理和简单使用,这个字段只会在
非别名的MappedHost
才有值 别名的MappedHost
,该属性为null
protected static final class MappedHost extends MapElement {
public volatile ContextList contextList;
/**
* Link to the "real" MappedHost, shared by all aliases.
* 真实的host,一个主机可能有多个别名。
* 所有`别名的MappedHost`共享一个`非别名的MappedHost`,并存放在这个属性中
*/
private final MappedHost realHost;
/**
* Links to all registered aliases, for easy enumeration. This field
* is available only in the "real" MappedHost. In an alias this field
* is null
.
* 1. `别名MappedHost列表`。
* 2. 为了统一处理和简单使用,这个字段只会在`非别名的MappedHost`才有值
* 3. `别名的MappedHost`,该属性为null
*/
private final List aliases;
/**
* Constructor used for the primary Host
*
* @param name The name of the virtual host
* @param host The host
*/
public MappedHost(String name, Host host) {
super(name, host);
realHost = this;
contextList = new ContextList();
aliases = new CopyOnWriteArrayList();
}
/**
* Constructor used for an Alias
*
* @param alias The alias of the virtual host
* @param realHost The host the alias points to
*/
public MappedHost(String alias, MappedHost realHost) {
super(alias, realHost.object);
this.realHost = realHost;
this.contextList = realHost.contextList;
this.aliases = null;
}
public boolean isAlias() {
return realHost != this;
}
public MappedHost getRealHost() {
return realHost;
}
public String getRealHostName() {
return realHost.name;
}
public Collection getAliases() {
return aliases;
}
public void addAlias(MappedHost alias) {
aliases.add(alias);
}
public void addAliases(Collection
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?