摘要:本文介绍在tomcat中部署项目的过程中涉及的类的源码。
在上一篇文章中,我们讲解了tomcat中项目部署的方式,以及tomcat中部署项目的时候的相关作用类。这篇文章我们就来查看下相关类HostConfig
是如何部署项目的。
从前篇文章中我们知道部署项目是在StandardHost
触发了其自身的start
生命周期时间,然后作为其监听器的HostConfig
类也触发自身的start()
方法,所以我们来先从HostConfig
类的start()
方法入手。
/**
* Process a "start" event for this Host.
*/
public void start() {
if (log.isDebugEnabled())
log.debug(sm.getString("hostConfig.start"));
try {
ObjectName hostON = host.getObjectName();
oname = new ObjectName
(hostON.getDomain() + ":type=Deployer,host=" + host.getName());
Registry.getRegistry(null, null).registerComponent
(this, oname, this.getClass().getName());
} catch (Exception e) {
log.error(sm.getString("hostConfig.jmx.register", oname), e);
}
if (!appBase().isDirectory()) {
log.error(sm.getString(
"hostConfig.appBase", host.getName(), appBase().getPath()));
host.setDeployOnStartup(false);
host.setAutoDeploy(false);
}
//111
if (host.getDeployOnStartup())
deployApps();
}
标注1的地方返回的true
,调用deployApps()
方法:
/**
* Deploy applications for any directories or WAR files that are found
* in our "application root" directory.
*/
protected void deployApps() {
//111
File appBase = appBase();
//2222
File configBase = configBase();
//3333
String[] filteredAppPaths = filterAppPaths(appBase.list());
// Deploy XML descriptors from configBase
// 发布 xml描述符
//4444
deployDescriptors(configBase, configBase.list());
// Deploy WARs
//发布 war包
//555555
deployWARs(appBase, filteredAppPaths);
// Deploy expanded folders
// 发布拓展文件夹
//666666
deployDirectories(appBase, filteredAppPaths);
}
我们从标注1的代码开始看,appBase()
方法:
/**
* Return a File object representing the "application root" directory
* for our associated Host.
*/
protected File appBase() {
if (appBase != null) {
return appBase;
}
appBase = returnCanonicalPath(host.getAppBase());
return appBase;
}
/**
* The application root for this Host.
*/
private String appBase = "webapps";
@Override
public String getAppBase() {
return (this.appBase);
}
protected File returnCanonicalPath(String path) {
File file = new File(path);
File base = new File(System.getProperty(Globals.CATALINA_BASE_PROP));
if (!file.isAbsolute())
file = new File(base,path);
try {
return file.getCanonicalFile();
} catch (IOException e) {
return file;
}
}
代码比较简单,可以看出appBase()
方法返回的是webapp
目录的绝对路径。
标注2的地方configBase()
:
/**
* Return a File object representing the "configuration root" directory
* for our associated Host.
*/
protected File configBase() {
if (configBase != null) {
return configBase;
}
if (host.getXmlBase()!=null) {
configBase = returnCanonicalPath(host.getXmlBase());
} else {
StringBuilder xmlDir = new StringBuilder("conf");
Container parent = host.getParent();
if (parent instanceof Engine) {
xmlDir.append('/');
xmlDir.append(parent.getName());
}
xmlDir.append('/');
xmlDir.append(host.getName());
configBase = returnCanonicalPath(xmlDir.toString());
}
return (configBase);
}
这个代码也比较简单,返回的是conf/Catalina
目录下对应host的路径,例如默认的host是localhost
,返回的就是conf/Catalina/localhost
这个路径的绝对路径。
标注3的filterAppPaths()
方法:
//首先传递的参数是webapp目录下的所有文件,包含文件夹和文件
protected String[] filterAppPaths(String[] unfilteredAppPaths) {
//返回Host的不需要发布的Context的匹配字符串,可以在<Host>标签中配置
Pattern filter = host.getDeployIgnorePattern();
//匹配模式是空,那么就返回webapp文件夹中的所有文件
if (filter == null || unfilteredAppPaths == null) {
return unfilteredAppPaths;
}
List<String> filteredList = new ArrayList<String>();
Matcher matcher = null;
//如果匹配模式非空,那么遍历webapp目录下的所有文件,过滤掉满足匹配模式的文件(也就是不需要被发布的文件或文件夹)
for (String appPath : unfilteredAppPaths) {
if (matcher == null) {
matcher = filter.matcher(appPath);
} else {
matcher.reset(appPath);
}
if (matcher.matches()) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("hostConfig.ignorePath", appPath));
}
} else {
filteredList.add(appPath);
}
}
//返回需要被发布的文件
return filteredList.toArray(new String[filteredList.size()]);
}
标注4的deployDescriptors()
方法:
/**
* Deploy XML context descriptors.
* configBase 指向的是 conf/Catalina/localhost 目录(默认)
* files 指的是 该目录下所有的file的名字
*/
protected void deployDescriptors(File configBase, String[] files) {
if (files == null)
return;
//获取host内部的线程池,线程池的初始化代码之前说过,或者自行到ContainerBase 类的init方法中查找。
ExecutorService es = host.getStartStopExecutor();
List<Future<?>> results = new ArrayList<Future<?>>();
//遍历localhost目录,该目录下应该都是context的xml文件,每个xml代表一个Context
for (int i = 0; i < files.length; i++) {
//父路径+文件 新建一个文件
File contextXml = new File(configBase, files[i]);
//文件名 判断
if (files[i].toLowerCase(Locale.ENGLISH).endsWith(".xml")) {
//对于localhost目录下的xml文件 新建一个代表其名称的ContextName对象
ContextName cn = new ContextName(files[i], true);
//判断 该应用是否已经在使用 或者已经发布过了,如果是就跳过
if (isServiced(cn.getName()) || deploymentExists(cn.getName()))
continue;
//提交一个发布 xml的任务
results.add(es.submit(new DeployDescriptor(this, cn, contextXml)));
}
}
for (Future<?> result : results) {
try {
//等待 发布xml任务完成
result.get();
} catch (Exception e) {
log.error(sm.getString(
"hostConfig.deployDescriptor.threaded.error"), e);
}
}
}
需要说明的是
-
es是Host内部的一个线程池,在
init()
的时候初始化,具体代码在ContainerBase
类的init()
方法中。 -
configBase
指向conf/Catalina/localhost
目录,files
目录指向的是configBase
类下的所有文件。 -
ContextName
是一个来表示一个Context名称的类,举例例如localhost
目录下有test.xml
文件,那么ContextName
对象如下: -
isServiced
和deploymentExists
两个方法具体内部代码很简单,但是变量都是host
的成员变量,填充的过程在DeployDescriptor
中。 -
DeployDescriptor
是一个可执行的任务,具体发布过程要参见其run()
方法。
DeployDescriptor
类的run()
方法:
private HostConfig config;
private ContextName cn;
private File descriptor;
public DeployDescriptor(HostConfig config, ContextName cn,
File descriptor) {
this.config = config;
this.cn = cn;
this.descriptor= descriptor;
}
@Override
public void run() {
config.deployDescriptor(cn, descriptor);
}
查看 StandardHost
类的deployDescriptor()
方法,有删减:
/**
* @param cn
* @param contextXml
*/
@SuppressWarnings("null") // context is not null
protected void deployDescriptor(ContextName cn, File contextXml) {
//代表已经发布的Application对象,传入名称,新建对象
DeployedApplication deployedApp = new DeployedApplication(cn.getName(), true);
long startTime = 0;
//需要被发布的Context对象
Context context = null;
//是否是外部war包 指war包不在默认的webapp目录下,是在xml指定的一个绝对路径下
boolean isExternalWar = false;
//是否是外部的目录 指war包解压出来的目录不在默认的webapp目录下,是在xml指定的一个绝对路径下
boolean isExternal = false;
File expandedDocBase = null;
FileInputStream fis = null;
try {
fis = new FileInputStream(contextXml);
synchronized (digesterLock) {
try {
//把 conf/Catalina/localhost/*.xml 解析成 Context对象(StandardContext)
context = (Context) digester.parse(fis);
} catch (Exception e) {
log.error(sm.getString(
"hostConfig.deployDescriptor.error",
contextXml.getAbsolutePath()), e);
context = new FailedContext();
} finally {
digester.reset();
}
}
//为xml解析出来的Context对象设置一些基本属性,例如监听器,名称,路径,版本等
Class<?> clazz = Class.forName(host.getConfigClass());
LifecycleListener listener =
(LifecycleListener) clazz.newInstance();
context.addLifecycleListener(listener);
context.setConfigFile(contextXml.toURI().toURL());
context.setName(cn.getName());
context.setPath(cn.getPath());
context.setWebappVersion(cn.getVersion());
// 如果xml配置的docBase非空
// Add the associated docBase to the redeployed list if it's a WAR
if (context.getDocBase() != null) {
File docBase = new File(context.getDocBase());
//如果xml的docBase配置的是相对路径
if (!docBase.isAbsolute()) {
//把docBase挂靠的默认目录下(webapp)
docBase = new File(appBase(), context.getDocBase());
}
// If external docBase, register .xml as redeploy first
//如果 xml的docBase的绝对路径开头 和 默认的appBase(webapp)目录不同,那么也就是xml默认的是拓展目录,非默认目录
if (!docBase.getCanonicalPath().startsWith(appBase().getAbsolutePath() + File.separator)) {
//拓展目录 设置为true
isExternal = true;
//redeployResources是deployApp对象的成员变量,是个map,将xml作为key,修改时间作为value放入到map中,该map具体的作用后续会说。(大概就是该map下的所有的变量会重新deploy一遍)
deployedApp.redeployResources.put(
contextXml.getAbsolutePath(),
Long.valueOf(contextXml.lastModified()));
//把docBase据对路径作为key,修改时间作为value 放入map中
deployedApp.redeployResources.put(docBase.getAbsolutePath(),
Long.valueOf(docBase.lastModified()));
//如果docBase配置的是war包,非目录,那么设置isExternalWar为true
if (docBase.getAbsolutePath().toLowerCase(Locale.ENGLISH).endsWith(".war")) {
isExternalWar = true;
}
} else {
log.warn(sm.getString("hostConfig.deployDescriptor.localDocBaseSpecified",
docBase));
// Ignore specified docBase
context.setDocBase(null);
}
}
//将该Context对象和host对象关联
host.addChild(context);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("hostConfig.deployDescriptor.error",
contextXml.getAbsolutePath()), t);
} finally {
//关闭读取流
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
// Ignore
}
}
// Get paths for WAR and expanded WAR in appBase
// default to appBase dir + name
//先给代表一个Context的文件夹(无论是最后被解压出来的或者一开始配置的就是文件夹)一个默认值,webapp+xml中配置的名称
expandedDocBase = new File(appBase(), cn.getBaseName());
//如果xml中docBase配置的不是war包,也就是配置的是指向context的文件夹
if (context.getDocBase() != null
&& !context.getDocBase().toLowerCase(Locale.ENGLISH).endsWith(".war")) {
// first assume docBase is absolute
//先默认xml配置的docBase是绝对路径
expandedDocBase = new File(context.getDocBase());
if (!expandedDocBase.isAbsolute()) {
// if docBase specified and relative, it must be relative to appBase
//如果xml配置的docBase 是相对路径,那么就托管到默认的docBase(webapp)目录下
expandedDocBase = new File(appBase(), context.getDocBase());
}
}
//先设置upackWAR变量为host配置下的upackWARs变量
boolean unpackWAR = unpackWARs;
//如果host的为true
if (unpackWAR && context instanceof StandardContext) {
//设置 变量为Context的 unpackWAR变量
unpackWAR = ((StandardContext) context).getUnpackWAR();
}
// Add the eventual unpacked WAR and all the resources which will be
// watched inside it
//如果配置的是war包
if (isExternalWar) {
//允许自动解压war包
if (unpackWAR) {
//将最终xml对应的context的文件夹防止到 redeployResources 中。
deployedApp.redeployResources.put(expandedDocBase.getAbsolutePath(),
Long.valueOf(expandedDocBase.lastModified()));
//将xml对应的context中的需要被reload的文件添加到 reload map中(方法很简单自行查看)
addWatchedResources(deployedApp, expandedDocBase.getAbsolutePath(), context);
} else {
addWatchedResources(deployedApp, null, context);
}
} else {
//如果xml docBase配置的不是war包
// Find an existing matching war and expanded folder
//如果不是外部的文件夹,也就是docBase配置的是相对路径 不是绝对路径 挂靠在webapp目录下
if (!isExternal) {
File warDocBase = new File(expandedDocBase.getAbsolutePath() + ".war");
//查看有没有默认的war包
if (warDocBase.exists()) {
deployedApp.redeployResources.put(warDocBase.getAbsolutePath(),
Long.valueOf(warDocBase.lastModified()));
} else {
// Trigger a redeploy if a WAR is added
//没有就把 扩展目录 添加到 redeploy map中,修改时间为0(默认会depoly一次)
deployedApp.redeployResources.put(
warDocBase.getAbsolutePath(),
Long.valueOf(0));
}
}
//允许解压
if (unpackWAR) {
//添加 context对应的目录到 redeploy map中
deployedApp.redeployResources.put(expandedDocBase.getAbsolutePath(),
Long.valueOf(expandedDocBase.lastModified()));
//将xml对应的context中的需要被reload的文件添加到 reload map中(方法很简单自行查看)
addWatchedResources(deployedApp,
expandedDocBase.getAbsolutePath(), context);
} else {
addWatchedResources(deployedApp, null, context);
}
//如果不是外部的文件夹,也就是docBase配置的是相对路径 不是绝对路径 挂靠在webapp目录下
if (!isExternal) {
// For external docBases, the context.xml will have been
// added above.
//将xml路径作为key 修改时间作为value 加入到 redeploy map中
deployedApp.redeployResources.put(
contextXml.getAbsolutePath(),
Long.valueOf(contextXml.lastModified()));
}
}
// Add the global redeploy resources (which are never deleted) at
// the end so they don't interfere with the deletion process
//添加全局的 需要 redeploy 文件
addGlobalRedeployResources(deployedApp);
}
//如果 host中包含该context 那么 把当前context放入 已经deployed map中
if (host.findChild(context.getName()) != null) {
deployed.put(context.getName(), deployedApp);
}
if (log.isInfoEnabled()) {
log.info(sm.getString("hostConfig.deployDescriptor.finished",
contextXml.getAbsolutePath(), Long.valueOf(System.currentTimeMillis() - startTime)));
}
}
到这里deployDescriptors()
方法就看完了,整体的流程是比较简单的,只不过加了很多的判断。整体流程就是将对应的xml文件解析成Context对象,然后设置该Context对象的一些基本属性,最后将这个代表xml的Context对象设置到父容器中,也就是StandardHost
对象。除了这些操作。tomcat在方法的最初会把需要被deploy的对象抽象为一个HostConfig$DeployedApplication
对象,然后在整个方法过程中向这个对象的两个成员变量添加数据。这个两个成员变量是redeployResources
和reloadResources
,而在将这些数据添加到HostConfig$DeployedApplication
对象后,tomcat在方法的最后将这个对象又添加到了StandardHost
对象另外一个成员变量deployed
中,在后续的分析中我们会继续分析这3个变量到底含义是什么。也许听的很乱,但是如果自行debug一下的话,会发现逻辑还是可以的。下面的图就是在conf/Catalina/localhost
配置了一个test.xml
然后debug deployDescriptor()
方法得到的图,如下:
//xml中配置:
<Context path="" reloadable="true" docBase="F:coooooooooooooooooooolTomcatSourceconfCatalinalocalhostapp"/>
关于这3个变量我们先暂时放一放,先继续看下面2个方法deployWARs()
和deployDirectories()
。
deployWARs()
/**
* Deploy WAR files.
*/
protected void deployWARs(File appBase, String[] files) {
if (files == null)
return;
ExecutorService es = host.getStartStopExecutor();
List<Future<?>> results = new ArrayList<Future<?>>();
for (int i = 0; i < files.length; i++) {
if (files[i].equalsIgnoreCase("META-INF"))
continue;
if (files[i].equalsIgnoreCase("WEB-INF"))
continue;
File war = new File(appBase, files[i]);
//文件必须是war包
if (files[i].toLowerCase(Locale.ENGLISH).endsWith(".war") &&
war.isFile() && !invalidWars.contains(files[i]) ) {
ContextName cn = new ContextName(files[i], true);
if (isServiced(cn.getName())) {
continue;
}
if (deploymentExists(cn.getName())) {
DeployedApplication app = deployed.get(cn.getName());
boolean unpackWAR = unpackWARs;
if (unpackWAR && host.findChild(cn.getName()) instanceof StandardContext) {
unpackWAR = ((StandardContext) host.findChild(cn.getName())).getUnpackWAR();
}
if (!unpackWAR && app != null) {
// Need to check for a directory that should not be
// there
File dir = new File(appBase, cn.getBaseName());
if (dir.exists()) {
if (!app.loggedDirWarning) {
log.warn(sm.getString(
"hostConfig.deployWar.hiddenDir",
dir.getAbsoluteFile(),
war.getAbsoluteFile()));
app.loggedDirWarning = true;
}
} else {
app.loggedDirWarning = false;
}
}
continue;
}
// Check for WARs with /../ /./ or similar sequences in the name
if (!validateContextPath(appBase, cn.getBaseName())) {
log.error(sm.getString(
"hostConfig.illegalWarName", files[i]));
invalidWars.add(files[i]);
continue;
}
//提交一个 发布war包的任务
results.add(es.submit(new DeployWar(this, cn, war)));
}
}
for (Future<?> result : results) {
try {
result.get();
} catch (Exception e) {
log.error(sm.getString(
"hostConfig.deployWar.threaded.error"), e);
}
}
}
方法内部就没写很多注释,基本都是增加一些额外的判断,记录日志等,在方法的最后提交了一个发布war包的任务,所以我们需要查看下DeployWar
类。
private static class DeployWar implements Runnable {
private HostConfig config;
private ContextName cn;
private File war;
public DeployWar(HostConfig config, ContextName cn, File war) {
this.config = config;
this.cn = cn;
this.war = war;
}
@Override
public void run() {
config.deployWAR(cn, war);
}
}
查看HostConfig
类的deployWAR()
方法(有删减)。
/**
* @param cn
* @param war
*/
protected void deployWAR(ContextName cn, File war) {
//略
Context context = null;
try {
if (deployXML && xml.exists() && unpackWARs && !copyXML) {
//略
} else if (deployXML && xmlInWar) {
//略
} else if (!deployXML && xmlInWar) {
//略
} else {
context = (Context) Class.forName(contextClass).newInstance();
}
} catch (Throwable t) {
} finally {
if (context == null) {
context = new FailedContext();
}
}
//略
//111111111
DeployedApplication deployedApp = new DeployedApplication(cn.getName(),
xml.exists() && deployXML && copyThisXml);
//略
try {
// Populate redeploy resources with the WAR file
deployedApp.redeployResources.put
(war.getAbsolutePath(), Long.valueOf(war.lastModified()));
if (deployXML && xml.exists() && copyThisXml) {
deployedApp.redeployResources.put(xml.getAbsolutePath(),
Long.valueOf(xml.lastModified()));
} else {
// In case an XML file is added to the config base later
deployedApp.redeployResources.put(
(new File(configBase(),
cn.getBaseName() + ".xml")).getAbsolutePath(),
Long.valueOf(0));
}
//2222222222
Class<?> clazz = Class.forName(host.getConfigClass());
LifecycleListener listener =
(LifecycleListener) clazz.newInstance();
context.addLifecycleListener(listener);
context.setName(cn.getName());
context.setPath(cn.getPath());
context.setWebappVersion(cn.getVersion());
context.setDocBase(cn.getBaseName() + ".war");
host.addChild(context);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("hostConfig.deployWar.error",
war.getAbsolutePath()), t);
} finally {
// If we're unpacking WARs, the docBase will be mutated after
// starting the context
boolean unpackWAR = unpackWARs;
if (unpackWAR && context instanceof StandardContext) {
unpackWAR = ((StandardContext) context).getUnpackWAR();
}
if (unpackWAR && context.getDocBase() != null) {
File docBase = new File(appBase(), cn.getBaseName());
deployedApp.redeployResources.put(docBase.getAbsolutePath(),
Long.valueOf(docBase.lastModified()));
addWatchedResources(deployedApp, docBase.getAbsolutePath(),
context);
if (deployXML && !copyThisXml && (xmlInWar || xml.exists())) {
deployedApp.redeployResources.put(xml.getAbsolutePath(),
Long.valueOf(xml.lastModified()));
}
} else {
// Passing null for docBase means that no resources will be
// watched. This will be logged at debug level.
addWatchedResources(deployedApp, null, context);
}
// Add the global redeploy resources (which are never deleted) at
// the end so they don't interfere with the deletion process
addGlobalRedeployResources(deployedApp);
}
//3333333333
deployed.put(cn.getName(), deployedApp);
if (log.isInfoEnabled()) {
log.info(sm.getString("hostConfig.deployWar.finished",
war.getAbsolutePath(), Long.valueOf(System.currentTimeMillis() - startTime)));
}
}
原方法是很长了,之所以很长很复杂是因为需要考虑的情况很多,需要兼容多重情况,多重配置参数。删减后发现跟之前的deployDescriptor()
相差不是很大,主要就是标注1的地方新建一个HostConfig$DeployedApplication
对象代表这个需要被deploy对象。标注2的地方新建Context对象代表war包解压出来的Context,设置该Context的一些基本属性,例如名称,路径,监听器等,并且把Context对象与父容器StandardHost
。标注3的地方把新建的HostConfig$DeployedApplication
对象添加到deployed
变量中,代表改Context对象已经被发布。
继续查看deployDirectories()
方法。
/**
* Deploy directories.
*/
protected void deployDirectories(File appBase, String[] files) {
if (files == null)
return;
ExecutorService es = host.getStartStopExecutor();
List<Future<?>> results = new ArrayList<Future<?>>();
for (int i = 0; i < files.length; i++) {
if (files[i].equalsIgnoreCase("META-INF"))
continue;
if (files[i].equalsIgnoreCase("WEB-INF"))
continue;
File dir = new File(appBase, files[i]);
if (dir.isDirectory()) {
ContextName cn = new ContextName(files[i], false);
if (isServiced(cn.getName()) || deploymentExists(cn.getName()))
continue;
//提交一个 发布任务 11111
results.add(es.submit(new DeployDirectory(this, cn, dir)));
}
}
for (Future<?> result : results) {
try {
result.get();
} catch (Exception e) {
log.error(sm.getString(
"hostConfig.deployDir.threaded.error"), e);
}
}
}
标注1的地方提交一个DeployDirectory
任务,查看DeployDirectory
类:
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);
}
}
查看HostConfig
类的deployDirectory()
方法(有删减):
/**
* @param cn
* @param dir
*/
protected void deployDirectory(ContextName cn, File dir) {
//略
Context context = null;
File xml = new File(dir, Constants.ApplicationContextXml);
File xmlCopy = new File(configBase(), cn.getBaseName() + ".xml");
DeployedApplication deployedApp;
boolean copyThisXml = copyXML;
try {
if (deployXML && xml.exists()) {
//略
} else if (!deployXML && xml.exists()) {
//略
} else {
context = (Context) Class.forName(contextClass).newInstance();
}
////11111
Class<?> clazz = Class.forName(host.getConfigClass());
LifecycleListener listener =
(LifecycleListener) clazz.newInstance();
context.addLifecycleListener(listener);
context.setName(cn.getName());
context.setPath(cn.getPath());
context.setWebappVersion(cn.getVersion());
context.setDocBase(cn.getBaseName());
host.addChild(context);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("hostConfig.deployDir.error",
dir.getAbsolutePath()), t);
} finally {
//22222
deployedApp = new DeployedApplication(cn.getName(),
xml.exists() && deployXML && copyThisXml);
// Fake re-deploy resource to detect if a WAR is added at a later
// point
deployedApp.redeployResources.put(dir.getAbsolutePath() + ".war",
Long.valueOf(0));
deployedApp.redeployResources.put(dir.getAbsolutePath(),
Long.valueOf(dir.lastModified()));
if (deployXML && xml.exists()) {
if (copyThisXml) {
deployedApp.redeployResources.put(
xmlCopy.getAbsolutePath(),
Long.valueOf(xmlCopy.lastModified()));
} else {
deployedApp.redeployResources.put(
xml.getAbsolutePath(),
Long.valueOf(xml.lastModified()));
// Fake re-deploy resource to detect if a context.xml file is
// added at a later point
deployedApp.redeployResources.put(
xmlCopy.getAbsolutePath(),
Long.valueOf(0));
}
} else {
// Fake re-deploy resource to detect if a context.xml file is
// added at a later point
deployedApp.redeployResources.put(
xmlCopy.getAbsolutePath(),
Long.valueOf(0));
if (!xml.exists()) {
deployedApp.redeployResources.put(
xml.getAbsolutePath(),
Long.valueOf(0));
}
}
addWatchedResources(deployedApp, dir.getAbsolutePath(), context);
// Add the global redeploy resources (which are never deleted) at
// the end so they don't interfere with the deletion process
addGlobalRedeployResources(deployedApp);
}
//33333
deployed.put(cn.getName(), deployedApp);
if( log.isInfoEnabled() ) {
log.info(sm.getString("hostConfig.deployDir.finished",
dir.getAbsolutePath(), Long.valueOf(System.currentTimeMillis() - startTime)));
}
}
流程基本类似,标注1新建Context对象,并且填充,并且关联父容器。标注2新建HostConfig$DeployedApplication
对象并且填充。标注3将HostConfig$DeployedApplication
对象添加到deployed
变量中。
三个发布的方法我们都看完了,但是现在还是有几个疑问,例如deployed
变量有什么作用,HostConfig$DeployedApplication
的含义,以及其成员变量redeployResources
和reloadResources
分别代表什么。我们继续来查看源码讨论。
HostConfig$DeployedApplication
类源码:
/**
* This class represents the state of a deployed application, as well as
* the monitored resources.
* 这个类表示了已经发布的应用的状态以及被监控的资源
*/
protected static class DeployedApplication {
public DeployedApplication(String name, boolean hasDescriptor) {
this.name = name;
this.hasDescriptor = hasDescriptor;
}
/**
* Application context path. The assertion is that
* (host.getChild(name) != null).
* 应用的context path
*/
public String name;
/**
* Does this application have a context.xml descriptor file on the
* host's configBase?
* 这个应用在Host中是否有配置context.xml描述符
*/
public final boolean hasDescriptor;
/**
* Any modification of the specified (static) resources will cause a
* redeployment of the application. If any of the specified resources is
* removed, the application will be undeployed. Typically, this will
* contain resources like the context.xml file, a compressed WAR path.
* The value is the last modification time.
任何对特定资源的修改将会导致应用的重新部署。如果一些特定的资源被移除,那么应用将会取消部署。通常map中会包含key为context.xml,war的路径,value为他们的修改时间
*/
public LinkedHashMap<String, Long> redeployResources =
new LinkedHashMap<String, Long>();
/**
* Any modification of the specified (static) resources will cause a
* reload of the application. This will typically contain resources
* such as the web.xml of a webapp, but can be configured to contain
* additional descriptors.
* The value is the last modification time.
任何对特定资源的修改将会导致应用的重新加载。通常map中会包含key为web.xml(但是也可以配置额外的描述符),value为他们的修改时间。
*/
public HashMap<String, Long> reloadResources =
new HashMap<String, Long>();
/**
* Instant where the application was last put in service.
* 应用开始服务的时间
*/
public long timestamp = System.currentTimeMillis();
/**
* In some circumstances, such as when unpackWARs is true, a directory
* may be added to the appBase that is ignored. This flag indicates that
* the user has been warned so that the warning is not logged on every
* run of the auto deployer.
*/
public boolean loggedDirWarning = false;
}
可以看到HostConfig$DeployedApplication
类代表了一个已经被发布的应用,而redeployResources
则代表了这个被发布的应用的各种路径(看下面的图就知道各种路径是什么意思了),以及他们的修改时间。而reloadResources
则代表了这个应用内部一些特殊资源,如果资源被修改,应用会重新加载,通常包含web.xml
。再查看deployed
源码:
/**
* Map of deployed applications.
*/
protected Map<String, DeployedApplication> deployed =
new ConcurrentHashMap<String, DeployedApplication>();
deployed
是个map,一个HostConfig
的成员变量,来记录所有被发布的应用集合。
那么问题就来了,HostConfig
记录了所有的被发布的应用,以及应用的修改时间,从注释上来看说的是如果应用的修改时间改变,那么应用会重新的deploy
,这个流程是如何实现的呢? 这个就留给读者自行查找了,提示下这个检测方法是个周期性的方法,通过实现LifecycleListener
来实现的周期性方法(如果最后用空的话 可以再一起分析一下,受制于篇幅过长)。
下图为deployDescriptors()
,deployWARs()
,deployDirectories()
方法全部执行完,最后跑出来的deployed
变量,其中配置了test.xml
,webapp
中放置了webapptest.war
。
总结:tomcat中发布项目通过StandardHost
的监听器HostConfig
完成。发布总计3种,发布xml
,发布war
包,发布文件夹。发布的核心内容就是新建Context
变量,然后将Context
变量与父类StandardHost
通过addChild()
方法来完成。
(完)