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

发布时间:2017-2-22 21:24:52 编辑:www.fx114.net 分享查询网我要评论
本篇文章主要介绍了"Tomcat中项目的部署以及其源码分析(二) ",主要涉及到Tomcat中项目的部署以及其源码分析(二) 方面的内容,对于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";@Overridepublic 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;[email protected] void run() {    config.deployDescriptor(cn, descriptor);}

查看 StandardHost类的deployDescriptor()方法,有删减:

 /** * @param cn * @param contextXml [email protected]("null") // context is not nullprotected 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:\cooooooooooooooooooool\TomcatSource\conf\Catalina\localhost\app"/>

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

(完)

上一篇:C#设计模式-策略者模式
下一篇:Lucene分页-----SearcherAfter

相关文章

相关评论