Tomcat 8 源码学习六之加载webapps
Contents
Tomcat中的层次
Catalina -> Server ---
|---globalNamingResources
|---namingContextListener
|---多个Service --------------------|
|---catalina.home |---Engine(每个Service最多一个) ---多个Container(比如host)
|---catalina.base
Tomcat有明显的层次关系上层只会对自己直接下一层的组件负责初始化,然后下一组件再对自己的子组件进行初始化.
可以看到,Tomcat中如果部署多个应用时,每一个Host,就代表了一主机.可以这样了比作:
Catalina或Server就是一个服务器.
Host就是该服务器上的一个虚拟主机.
Application就是虚拟主机上的一个应用.(webapss目录下的每一个目录就对应一个application,在Tomcat里,用 org.apache.catalina.startup.HostConfig 类的内部类 DeployedApplication 来表示.)
初始化一个 Host
每一个Host,都有一个org.apache.catalina.startup.HostConfig
对象代表.它会根据配置文件conf/server.xml
中的<host>
节点相匹配.
部署的代码为:
protected void deployApps() {
File appBase = host.getAppBaseFile();
File configBase = host.getConfigBaseFile();
String[] filteredAppPaths = filterAppPaths(appBase.list());
// Deploy XML descriptors from configBase
deployDescriptors(configBase, configBase.list());
// Deploy WARs
deployWARs(appBase, filteredAppPaths);
// Deploy expanded folders
deployDirectories(appBase, filteredAppPaths);
}
部署 deployDescriptors
先部署deployDescriptors()
,它的描述文件目录在conf/Catalina/localhost
这个虚拟主机目录下.比如,创建一个文件名为myapp.xml
,内容为:
<Context path="/myapp" docBase="/home/sky/ROOT" debug="0" privileged="true"></Context>
它就会根据该目录下的myapp.xml
文件配置来部署应用.比如上面的例子,它会部署一个path
为/myapp
, 部署的目录为/home/sky/ROOT
.这样子,就可以通过http://localhost:8080/myapp
来访问了.
这种方式,可以不必将应用程序的目录,放到webapps
目录下.
部署 wars
它会根据在配置文件conf/server.xml
中的<host>
的appBase
属性的值所在的目录(默认为webapps)下查找.war
结尾的文件, 然后再判断是否需要解压(根据配置文件中的 unpackWARs
属性).
如果解压了,就和部署目录的逻辑是一样的.
部署 目录
它的代码如下,可以看到,对于每个目录,它是通过多线程来进行部署的.每一个目录都有一条线程去负责部署.而且是利用Host的getStartStopExecutor
来执行的.
private static class DeployDirectory implements Runnable {
private HostConfig config;
private ContextName cn;
private File dir;
public DeployDirectory(HostConfig config, ContextName cn, File dir) {
this.config = config;
this.cn = cn;
this.dir = dir;
}
@Override
public void run() {
config.deployDirectory(cn, dir);
}
}
部署的主要逻辑,是初始化一个StandardContext
对象(还带有一个 ContextConfig
对象, 这个config对象,就是web.xml
的代表. ).代表该应用的上下文对象.然后将host的listener(相当于host对所有应用都生效的listener,即对host来说是全局应用到每个应用的)添加到该application
的Listener生命周期中.代码逻辑如下:
Class<?> clazz = Class.forName(host.getConfigClass());
LifecycleListener listener = (LifecycleListener) clazz.newInstance(); # 这里就添加了一个 ContextConfig 对象.它随着 StandardContext的生命周期一起存灭.
context.addLifecycleListener(listener);
context.setName(cn.getName());
context.setPath(cn.getPath());
context.setWebappVersion(cn.getVersion());
context.setDocBase(cn.getBaseName());
host.addChild(context);
cn为ContextName
,它的构造逻辑如下:
根据webapps
目录下的目录名,创建相应的应用名
(里面有个特殊判断,如果目录名为ROOT,这个名字是硬编码写死的.那么它的path为”/“,即不需要通过应用名即可访问.)
ContextConfig 类
它会解析 StandardContext 以及 Webxml , 逻辑在 init 方法中:
protected void init() {
// Called from StandardContext.init()
Digester contextDigester = createContextDigester();
contextDigester.getParser();
if (log.isDebugEnabled()) {
log.debug(sm.getString("contextConfig.init"));
}
context.setConfigured(false);
ok = true;
contextConfig(contextDigester);
webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),
context.getXmlValidation(), context.getXmlBlockExternal()); # 解析web.xml
}
它和解析Tomcat的conf/server.xml
这些类似,都是初始化相应的组件.
web.xml
所有组件,都在org.apache.tomcat.util.descriptor.web.WebRuleSet
里可以看到.(这里只列出部分,以免占太多篇幅)
digester.addCallMethod(fullPrefix + "/servlet/servlet-class",
"setServletClass", 0);
digester.addCallMethod(fullPrefix + "/servlet/servlet-name",
"setServletName", 0);
digester.addObjectCreate(fullPrefix + "/servlet/multipart-config",
"org.apache.tomcat.util.descriptor.web.MultipartDef");
digester.addSetNext(fullPrefix + "/servlet/multipart-config",
"setMultipartDef",
"org.apache.tomcat.util.descriptor.web.MultipartDef");
digester.addCallMethod(fullPrefix + "/servlet/multipart-config/location",
"setLocation", 0);
比如Filter,Listener, Session参数等等.
web.xml 的对应类 WebXml
WebXml
类是web.xml
的对应代表类.
各个 web.xml 版本对应的J2EE版本.
org.apache.tomcat.util.descriptor.DigesterFactory
在这人类有明确的对照表.