1 Container容器
Container容器用来表示tomcat中servlet容器,负责servelt的加载和管理,处理请求ServletRequest,并返回标准的 ServletResponse 对象给连接器。
Container容器组件
tomcat 将Container容器按功能分为4个组件,分别是 Engine、Host、Context 和 Wrapper。这 4 种容器不是平行关系,而是父子关系。
Wrapper:表示一个 Servlet
Context:表示一个 Web 应用程序,一个 Web 应用程序中可能会有多个 Servlet
Host:表示的是一个虚拟主机,或者说一个站点,可以给 Tomcat 配置多个虚拟主机地址,而一个虚拟主机下可以部署多个 Web 应用程序
Engine:表示引擎,用来管理多个虚拟站点,一个 Service 最多只能有一个 Engine。
2 StandardHost类结构
2.1 Lifecycle接口
public interface Lifecycle {
....
// 初始化方法
public void init() throws LifecycleException;
// 启动方法
public void start() throws LifecycleException;
// 停止方法,和start对应
public void stop() throws LifecycleException;
// 销毁方法,和init对应
public void destroy() throws LifecycleException;
// 获取生命周期状态
public LifecycleState getState();
// 获取字符串类型的生命周期状态
public String getStateName();
}
Lifecycle接口定义tomcat中所有组件的生命周期相关接口方法。Tomcat 定义一个基类LifecycleBase 来实现 Lifecycle 接口,把一些公共的逻辑放到基类中实现。而子类就负责实现自己的初始化、启动和停止等模板方法。
详见 Tomcat架构设计-组件生命周期 Lifecycle
2.2 Container接口
public interface Container extends Lifecycle {
public static final String ADD_CHILD_EVENT = "addChild";
public static final String ADD_VALVE_EVENT = "addValve";
public static final String REMOVE_CHILD_EVENT = "removeChild";
public static final String REMOVE_VALVE_EVENT = "removeValve";
//返回日志组件
public Log getLogger();
//返回日志名称
public String getLogName();
//返回容器注册到JMX bean ObjectName
public ObjectName getObjectName();
//返回容器注册到JMX bean 命名空间
public String getDomain();
//返回容器注册到JMX bean 属性
public String getMBeanKeyProperties();
//返回容器依赖Pipeline组件
public Pipeline getPipeline();
//返回容器依赖Cluster组件
public Cluster getCluster();
//设置容器依赖Cluster组件
public void setCluster(Cluster cluster);
//返回周期性任务执行间隔事件
public int getBackgroundProcessorDelay();
//设置周期性任务执行间隔事件
public void setBackgroundProcessorDelay(int delay);
//返回容器名称
public String getName();
//设置容器名称
public void setName(String name);
//返回父容器
public Container getParent();
//设置父容器
public void setParent(Container container);
//返回父类加载器
public ClassLoader getParentClassLoader();
//设置父类加载器
public void setParentClassLoader(ClassLoader parent);
//返回容器依赖Realm组件
public Realm getRealm();
// 设置容器依赖Realm组件
public void setRealm(Realm realm);
//容器默认周期性任务处理调用方法
public void backgroundProcess();
//为当前容器组件添加子容器组件
public void addChild(Container child);
//添加容器事件监听器
public void addContainerListener(ContainerListener listener);
//添加属性变更监听器
public void addPropertyChangeListener(PropertyChangeListener listener);
//查找指定名称的子容器
public Container findChild(String name);
//获取所有子容器组件
public Container[] findChildren();
//返回所有容器事件监听器
public ContainerListener[] findContainerListeners();
//删除子容器
public void removeChild(Container child);
//当前容器删除容器事件监听器
public void removeContainerListener(ContainerListener listener);
//当前容器删除属性变更监听器
public void removePropertyChangeListener(PropertyChangeListener listener);
//处理容器事件
public void fireContainerEvent(String type, Object data);
//使用AccessLog组件打印请求日志
public void logAccess(Request request, Response response, long time,
boolean useDefault);
//返回访问日志组件AccessLog
public AccessLog getAccessLog();
//返回设置处理子容器启动关闭线程池核心线程数。
public int getStartStopThreads();
//设置处理子容器启动关闭线程池核心线程数。
public void setStartStopThreads(int startStopThreads);
//返回tomcat工作目录
public File getCatalinaBase();
//返回tomcat安装目录
public File getCatalinaHome();
}
Container接口定义tomcat中所有容器组件的通用接口方法。Tomcat 定义一个基类ContainerBase 来实现Container 接口,把一些公共的逻辑放到基类中实现。
详见 Tomcat架构设计-容器组件基类 ContainerBase
3 Tomcat中虚拟主机Host
在tomcat中最核心功能就是将一个静态资源目录或一个应用程序部署到容器中。而这个容器就是指得Host容器组件。而静态资源或一个应用程序通过Context容器组件来表示。所谓部署就是加载到Host容器的子组件中。当然虚拟主机除了部署外还又其他功能,包括热部署,懒加载,别名等。
3.1 部署静态资源
如果想要将一个静态资源目录部署到Tomcat服务器上,tomcat提供了多种部署方式
在server.xml中配置
path表示Context根路径,docBase表示映射静态资源目录
在xmlBase路径下配置xml文件
在$CATALINA_BASE/xmlBase 路径下创建 JavaWebApp.xml,xmlBase配置在Host标签属性中
文件名称表示Context根路径,docBase表示映射静态资源目录
将资源文件拷贝到appBase路径下
appBase路径在Host标签属性中定义,文件名称表示Context根路径。
3.2 部署应用程序
部署应用程序到appBase目录
appBase是在server.xml文件Host标签appBase属性来定义,appBase可以填写相对路径或者绝对路径,如果是相对路径那么完整路径为CATALINA_BASE表示tomcat的工作目录
3.3 懒部署
虚拟主机Host可以在设置在使用时在部署静态资源或应用程序。
3.4 热部署
虚拟主机Host会定期检查appBase和xmlBase目录下新Web应用程序或静态资源,如果发生更新则会触发对应context组件的重新加载
3.5 别名
虚拟主机Host可以定义别名。
3.6 管理子容器组件
StandardHost并管理子容器Context组件,以及从父类ContainerBase,LifecycleBase 继承的通用组件。
4 StandardHost职能&属性
StandardHost实现了Host接口,在了解StandardHost功能之前我们需要了解
Host接口.
Host接口
public interface Host extends Container {
public static final String ADD_ALIAS_EVENT = "addAlias";
public static final String REMOVE_ALIAS_EVENT = "removeAlias";
/**
* 返回配置文件相对路径,配置文件完整路径为$catalinaBase/xmlBase
*/
public String getXmlBase();
/**
* 设置配置文件相对路径
*/
public void setXmlBase(String xmlBase);
/**
* 返回配置文件对象,对应完整路径为$catalinaBase/xmlBase
*/
public File getConfigBaseFile();
/**
* 返回扫描的应用程序相对路径或绝对路径,如果是相对路径则完整路径为$catalinaHome/appBase
*/
public String getAppBase();
/**
* 设置扫描的应用程序相对路径或绝对路径
*/
public void setAppBase(String appBase);
/**
* 返回扫描应用程序目录的文件对象
*/
public File getAppBaseFile();
/**
* 是否开启热部署
*/
public boolean getAutoDeploy();
/**
* 设置是否支持热部署
*/
public void setAutoDeploy(boolean autoDeploy);
/**
* 获取子组件Context配置实现类,默认org.apache.catalina.startup.ContextConfig
*/
public String getConfigClass();
/**
* 设置子组件Context配置实现类
*/
public void setConfigClass(String configClass);
/**
* 返回是否在启动Host组件时是否应自动部署Host组件的Web应用程序
*/
public boolean getDeployOnStartup();
/**
* 设置是否在启动Host组件时是否应自动部署Host组件的Web应用程序
*/
public void setDeployOnStartup(boolean deployOnStartup);
/**
* 返回正则表达式,用String表示,用来定义自动部署哪些应用程序
*/
public String getDeployIgnore();
/**
* 返回正则表达式,用来定义自动部署哪些应用程序
*/
public Pattern getDeployIgnorePattern();
/**
* 设置正则表达式,用String表示,用来定义自动部署哪些应用程序
*/
public void setDeployIgnore(String deployIgnore);
/**
* 返回处理子容器启动关闭线程池
*/
public ExecutorService getStartStopExecutor();
/**
* 返回是否需要在启动时创建appbase和xmlbase目录
*/
public boolean getCreateDirs();
/**
* 设置是否需要在启动时创建appbase和xmlbase目录
*/
public void setCreateDirs(boolean createDirs);
/**
* 返回是否检查现在可以取消部署的旧版本的应用程序
*/
public boolean getUndeployOldVersions();
/**
* 设置是否检查现在可以取消部署的旧版本的应用程序
*/
public void setUndeployOldVersions(boolean undeployOldVersions);
/**
* 给Host组件添加别名
*/
public void addAlias(String alias);
/**
* 返回Host组件所有别名
*/
public String[] findAliases();
/**
* 给Host组件删除别名
*/
public void removeAlias(String alias);
}
StandardHost实现Host接口,Host接口用来对Tomcat中虚拟主机功能配置提供了访问方法。
4.1 StandardHost职能
StandardHost只对虚拟机功能配置做了定义,其具体实现由HostConfig来实现。同时负责管理子容器Context组件(下图蓝色),以及从父类ContainerBase(下图红色),LifecycleBase(下图黄色) 继承的通用组件。
4.2 核心属性
/**
* Host组件别名
*/
private String[] aliases = new String[0];
/**
* 处理Host组件别名同步锁对象
*/
private final Object aliasesLock = new Object();
/**
* appBase表示扫描的应用程序相对路径或绝对路径,如果是相对路径则完整路径为$catalinaHome/appBase
*/
private String appBase = "webapps";
/**
* appBaseFile表示扫描的应用程序目录的文件对象
*/
private volatile File appBaseFile = null;
/**
* xmlBase表示配置文件相对路径,配置文件路径为$catalinaBase/xmlBase
*/
private String xmlBase = null;
/**
* hostConfigBase表示配置文件对象,对应路径为$catalinaBase/xmlBase
*/
private volatile File hostConfigBase = null;
/**
* 是否支持热部署
* 如果为true,虚拟主机Host会定期检查appBase和xmlBase目录下新Web应用程序或静态资源,如果发生更新则会触发对应context组件的重新加载
*/
private boolean autoDeploy = true;
/**
* 子组件Context配置实现类
*/
private String configClass =
"org.apache.catalina.startup.ContextConfig";
/**
* 子组件Context实现类
*/
private String contextClass =
"org.apache.catalina.core.StandardContext";
/**
* 标识在启动Host组件时是否应自动部署Host组件的Web应用程序。标志的值默认为true。
*/
private boolean deployOnStartup = true;
/**
* 是否要禁止应用程序中定义/META-INF/context.xml
*/
private boolean deployXML = !Globals.IS_SECURITY_ENABLED;
/**
* 如果在应用程序中定义了/META-INF/context.xml,是否要拷贝到$catalinaBase/xmlBase目录下
*/
private boolean copyXML = false;
/**
* Host组件子组件Pilpline组件内处理异常Valve实现类
*/
private String errorReportValveClass =
"org.apache.catalina.valves.ErrorReportValve";
/**
* 是否解压war包种应用程序在执行,默认为true
*/
private boolean unpackWARs = true;
/**
* 标识host组件工作的临时目录
* $catalinaBase/workDir
*/
private String workDir = null;
/**
* 标识是否需要在启动时创建appbase和xmlbase目录
*/
private boolean createDirs = true;
5 StandardHost运行流程
tomcat中所有组件都需要经历如下流程。
5.1 构建StandardHost
Tomcat使用Digester解析server.xml,Digester是一款用于将xml转换为Java对象的事件驱动型工具,是对SAX的高层次的封装。相对于SAX,Digester可以针对每一个xml标签设置对应的解析规则。详见 Tomcat相关技术-Digester(二)
Tomcat在Catalina组件初始化阶段调用createStartDigester()创建Digester对象,Digester对象内包含解析server.xml规则,接着通过Digester对象解析server.xml实例化StandardHost,并对部分属性设置值.
server.xml配置
解析
5.1.1 解析标签
//解析标签
/** 解析标签实例化StandardHost对象,并push到操作栈中 **/
digester.addObjectCreate(prefix + "Host",
"org.apache.catalina.core.StandardHost",
"className");
/** 解析标签将标签中属性值映射到其实例化对象中**/
digester.addSetProperties(prefix + "Host");
/** 解析标签,使用CopyParentClassLoaderRule规则,负责调用次栈顶对象getParentClassLoader获取父类加载,设置到栈顶对象parentClassLoader属性上 **/
digester.addRule(prefix + "Host",
new CopyParentClassLoaderRule());
/** 解析标签,使用LifecycleListenerRule规则,负责给栈顶对象添加一个生命周期监听器. 默认为hostConfigClass,或者在标签指定org.apache.catalina.startup.HostConfig属性**/
digester.addRule(prefix + "Host",
new LifecycleListenerRule
("org.apache.catalina.startup.HostConfig",
"hostConfigClass"));
/** 解析标签将操作栈栈顶对象作为次栈顶对象StandardService.addChild方法调用的参数,即将实例化StandardHost对象添加StandardServer.child子容器列表属性中**/
digester.addSetNext(prefix + "Host",
"addChild",
"org.apache.catalina.Container");
5.1.2 解析标签
/** 解析标签实例化标签中className属性定义的对象,并push到操作栈中 **/
digester.addObjectCreate(prefix + "Host/Listener",
null, // MUST be specified in the element
"className");
/** 解析标签将标签中属性值映射到其实例化对象中**/
digester.addSetProperties(prefix + "Host/Listener");
/** 解析 标签将操作栈栈顶对象作为次栈顶对象StandardHost.addLifecycleListener方法调用的参数,设置到StandardHost属性中**/
digester.addSetNext(prefix + "Host/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
5.1.3 解析标签
/** 解析标签实例化标签中className属性定义的对象,并push到操作栈中 **/
digester.addObjectCreate(prefix + "Host/Valve",
null,
"className");
/** 解析标签将标签中属性值映射到其实例化对象中**/
digester.addSetProperties(prefix + "Host/Valve");
/** 解析 标签将操作栈栈顶对象作为次栈顶对象StandardHost.addValve方法调用的参数,设置到StandardHost属性中**/
digester.addSetNext(prefix + "Host/Valve",
"addValve",
"org.apache.catalina.Valve");
5.1.4 解析标签,
/** 解析标签,将标签中数据test做为参数调用栈顶对象StandardHost.addAlias方法调用的参数,设置到StandardHost属性中 **/
digester.addCallMethod(prefix + "Host/Alias",
"addAlias", 0);
5.1.5 解析标签
//解析标签
digester.addRuleSet(new RealmRuleSet(prefix + "Host/"));
/**
* 解析Realm标签添加到栈顶对象属性中
*/
@SuppressWarnings("deprecation")
public class RealmRuleSet extends RuleSetBase {
...省略代码
@Override
public void addRuleInstances(Digester digester) {
StringBuilder pattern = new StringBuilder(prefix);
for (int i = 0; i < MAX_NESTED_REALM_LEVELS; i++) {
if (i > 0) {
pattern.append('/');
}
pattern.append("Realm");
addRuleInstances(digester, pattern.toString(), i == 0 ? "setRealm" : "addRealm");
}
}
private void addRuleInstances(Digester digester, String pattern, String methodName) {
digester.addObjectCreate(pattern, null /* MUST be specified in the element */,
"className");
digester.addSetProperties(pattern);
digester.addSetNext(pattern, methodName, "org.apache.catalina.Realm");
digester.addRuleSet(new CredentialHandlerRuleSet(pattern + "/"));
}
5.1.6 重要的规则
CopyParentClassLoaderRule规则
CopyParentClassLoaderRule规则,负责调用次栈顶对象getParentClassLoader获取父类加载,设置到栈顶对象parentClassLoader属性上
public class CopyParentClassLoaderRule extends Rule {
public CopyParentClassLoaderRule() {
}
@Override
public void begin(String namespace, String name, Attributes attributes)
throws Exception {
if (digester.getLogger().isDebugEnabled())
digester.getLogger().debug("Copying parent class loader");
Container child = (Container) digester.peek(0);
Object parent = digester.peek(1);
Method method =
parent.getClass().getMethod("getParentClassLoader", new Class[0]);
ClassLoader classLoader =
(ClassLoader) method.invoke(parent, new Object[0]);
child.setParentClassLoader(classLoader);
}
}
LifecycleListenerRule规则
LifecycleListenerRule 规则负责给栈顶对象添加一个生命周期监听器.
/**
* 解析标签给栈顶对象添加一个生命周期监听器
*/
public class LifecycleListenerRule extends Rule {
public LifecycleListenerRule(String listenerClass, String attributeName) {
this.listenerClass = listenerClass;
this.attributeName = attributeName;
}
/**
* 标准中指定属性,用来设置监听器处理类
*/
private final String attributeName;
/**
* 默认监听器处理类
*/
private final String listenerClass;
@Override
public void begin(String namespace, String name, Attributes attributes)
throws Exception {
/** 获取栈顶原始对象 **/
Container c = (Container) digester.peek();
/** 获取次栈顶元素对象 **/
Container p = null;
Object obj = digester.peek(1);
/** 如果栈顶元素对象是容器设置给p **/
if (obj instanceof Container) {
p = (Container) obj;
}
String className = null;
/** 获取标签attributeName值赋值给className **/
if (attributeName != null) {
String value = attributes.getValue(attributeName);
if (value != null)
className = value;
}
/** 获取次栈顶对象attributeName属性值赋值给className **/
if (p != null && className == null) {
String configClass =
(String) IntrospectionUtils.getProperty(p, attributeName);
if (configClass != null && configClass.length() > 0) {
className = configClass;
}
}
/** 如果className == null使用listenerClass作为className默认值**/
if (className == null) {
className = listenerClass;
}
/** 实例化className添加栈顶对象生命周期监听器列表中*/
Class> clazz = Class.forName(className);
LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
c.addLifecycleListener(listener);
}
}
组件生命周期
接下来初始化开始则进入tomcat组件的生命周期,对于tomcat中所有组件都必须实现Lifecycle,Tomcat 定义一个基类LifecycleBase 来实现 Lifecycle 接口,把一些公共的逻辑放到基类中实现,比如生命状态的转变与维护、生命事件的触发以及监听器的添加和删除等,而子类就负责实现自己的初始化、启动和停止等模板方法。为了避免跟基类中的方法同名,我们把具体子类的实现方法改个名字,在后面加上 Internal,叫 initInternal、startInternal 等。
StandardHost父类对容器的初始化、启动和停止等模板方法进行的了默认实现。子类容器只需要重写父类实现即可实现扩展。
5.2 启动StandardHost
/**
* 启动组件模板实现
*/
@Override
protected synchronized void startInternal() throws LifecycleException {
/** 将errorReportValveClass 类对象添加到Host组件Pipeline组件内 **/
String errorValve = getErrorReportValveClass();
if ((errorValve != null) && (!errorValve.equals(""))) {
try {
boolean found = false;
Valve[] valves = getPipeline().getValves();
for (Valve valve : valves) {
if (errorValve.equals(valve.getClass().getName())) {
found = true;
break;
}
}
if(!found) {
Valve valve =
(Valve) Class.forName(errorValve).getConstructor().newInstance();
getPipeline().addValve(valve);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString(
"standardHost.invalidErrorReportValveClass",
errorValve), t);
}
}
super.startInternal();
}
StandardEngine其他生命周期实现均从父类ContainerBase继承。
6 核心方法
6.1 重写addChild
为添加的子容器设置生命周期监听器MemoryLeakTrackingListener
/**
* 添加一个子容器
*/
@Override
public void addChild(Container child) {
/** 给子容器组件添加MemoryLeakTrackingListener监听器 **/
child.addLifecycleListener(new MemoryLeakTrackingListener());
if (!(child instanceof Context))
throw new IllegalArgumentException
(sm.getString("standardHost.notContext"));
super.addChild(child);
}
/**
* 处理AFTER_START_EVENT生命周期事件,设置childClassLoaders属性
*/
private class MemoryLeakTrackingListener implements LifecycleListener {
@Override
public void lifecycleEvent(LifecycleEvent event) {
if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
if (event.getSource() instanceof Context) {
Context context = ((Context) event.getSource());
childClassLoaders.put(context.getLoader().getClassLoader(),
context.getServletContext().getContextPath());
}
}
}
}
6.2 管理别名
/**
* 给host组件添加别名
*/
@Override
public void addAlias(String alias) {
alias = alias.toLowerCase(Locale.ENGLISH);
synchronized (aliasesLock) {
// Skip duplicate aliases
for (int i = 0; i < aliases.length; i++) {
if (aliases[i].equals(alias))
return;
}
// Add this alias to the list
String newAliases[] = new String[aliases.length + 1];
for (int i = 0; i < aliases.length; i++)
newAliases[i] = aliases[i];
newAliases[aliases.length] = alias;
aliases = newAliases;
}
/** 触发属性变更 **/
fireContainerEvent(ADD_ALIAS_EVENT, alias);
}
/**
* 返回host组件所有别名
*/
@Override
public String[] findAliases() {
synchronized (aliasesLock) {
return (this.aliases);
}
}
/**
* 删除host组件别名
*/
@Override
public void removeAlias(String alias) {
alias = alias.toLowerCase(Locale.ENGLISH);
synchronized (aliasesLock) {
int n = -1;
for (int i = 0; i < aliases.length; i++) {
if (aliases[i].equals(alias)) {
n = i;
break;
}
}
if (n < 0)
return;
int j = 0;
String results[] = new String[aliases.length - 1];
for (int i = 0; i < aliases.length; i++) {
if (i != n)
results[j++] = aliases[i];
}
aliases = results;
}
// Inform interested listeners
fireContainerEvent(REMOVE_ALIAS_EVENT, alias);
}
7 处理流程
每一个容器组件都有一个 Pipeline 对象,Pipeline 中维护了 Valve 链表,默认时每一个Pipeline存放了一个默认的BasicValue,
这里每一个Value表示一个处理点,当调用addValve 方法时会将添加Vaule添加到链表头部,Pipeline 中没有 invoke
方法,请求处理时Pipeline只需要获取链表中第一个Valve调用incoke去执行,执行完毕后当前Value会调用
getNext.invoke() 来触发下一个 Valve 调用
每一个容器在执行到最后一个默认BasicValue时,会负责调用下层容器的 Pipeline 里的第一个 Valve
对于StandardHost容器来说默认情况存在三个Value(阀门),分别是AccessLogValve(构建时读取server.xml时),StandardHostValve(构建实例化时),ErrorReportValve(启动时)。
7.1 ErrorReportValve
public class ErrorReportValve extends ValveBase {
private boolean showReport = true;
private boolean showServerInfo = true;
Constructor
public ErrorReportValve() {
super(true);
}
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
// 调用下一个Value处理
getNext().invoke(request, response);
/** 是否已提交此响应的输出 **/
if (response.isCommitted()) {
/** 未处理以提交设置响应错误**/
if (response.setErrorReported()) {
/** 清理response缓冲区 **/
try {
response.flushBuffer();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
}
/** response 设置关闭连接发出错误**/
response.getCoyoteResponse().action(ActionCode.CLOSE_NOW,
request.getAttribute(RequestDispatcher.ERROR_EXCEPTION));
}
return;
}
/** 获取异常 **/
Throwable throwable = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
/** 如果是异步处理直接返回 **/
if (request.isAsync() && !request.isAsyncCompleting()) {
return;
}
/** 发生一次设置http响应编码 500**/
if (throwable != null && !response.isError()) {
response.reset();
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
/** 设置挂起的标志。**/
response.setSuspended(false);
/** 打印错误报告 **/
try {
report(request, response, throwable);
} catch (Throwable tt) {
ExceptionUtils.handleThrowable(tt);
}
}
...省略代码
7.2 AccessLogValve
记录访问日志,这里是一个通用组件,后续会由专题讲解