Tomcat中项目的部署以及其源码分析(二)

摘要:本文介绍在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对象如下:

  • isServiceddeploymentExists两个方法具体内部代码很简单,但是变量都是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对象,然后在整个方法过程中向这个对象的两个成员变量添加数据。这个两个成员变量是redeployResourcesreloadResources,而在将这些数据添加到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的含义,以及其成员变量redeployResourcesreloadResources分别代表什么。我们继续来查看源码讨论。

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()方法来完成。

(完)

原文地址:https://www.cnblogs.com/coldridgeValley/p/6203270.html