Tomcat源码分析 之 Tomcat如何监测部署应用的改变并进行重新部署

来源:互联网 发布:盛势网络剧最新消息 编辑:程序博客网 时间:2024/06/05 03:21

使用eclipse和tomcat做web应用开发的童鞋都知道,有一个很强大的功能就是--我们的文件改变的时候Tomcat会自动的重新部署应用,给我们的开发调试带来了很大的便利,但是它是怎么实现的呢?我们下面来关注一下原理和Tomcat的处理办法。

我们都知道,我们修改了文件,并且保存了它,这个文件的修改时间就会被改变,当然了我们的应用处理逻辑也可能发生了变化,这时候我们就应该重新发布一下应用,让它切换到最新的逻辑上面。那么我们现在我们可以归结出Tomcat需要做两件事:

1.检测部署的应用文件是否被修改。

2.如果修改了那么需要重新部署应用。

  关于如何检测文件状态的变化可以参考《使用commons-vfs监听文件系统》 使用了里面的第二种方法。可以看Tomcat的源代码,这里使用的是8.X:

 这里的检测是在org.apache.catalina.startup.HostConfig中完成的:

首先添加要观察的文件:

 /**     * Add watched resources to the specified Context.     * @param app HostConfig deployed app     * @param docBase web app docBase     * @param context web application context     */    protected void addWatchedResources(DeployedApplication app, String docBase,            Context context) {        // FIXME: Feature idea. Add support for patterns (ex: WEB-INF/*,        //        WEB-INF/*.xml), where we would only check if at least one        //        resource is newer than app.timestamp        File docBaseFile = null;        if (docBase != null) {            docBaseFile = new File(docBase);            if (!docBaseFile.isAbsolute()) {                docBaseFile = new File(host.getAppBaseFile(), docBase);            }        }        String[] watchedResources = context.findWatchedResources();        for (int i = 0; i < watchedResources.length; i++) {            File resource = new File(watchedResources[i]);            if (!resource.isAbsolute()) {                if (docBase != null) {                    resource = new File(docBaseFile, watchedResources[i]);                } else {                    if(log.isDebugEnabled())                        log.debug("Ignoring non-existent WatchedResource '" +                                resource.getAbsolutePath() + "'");                    continue;                }            }            if(log.isDebugEnabled())                log.debug("Watching WatchedResource '" +                        resource.getAbsolutePath() + "'");            app.reloadResources.put(resource.getAbsolutePath(),                    Long.valueOf(resource.lastModified()));        }    }

其次是检测文件时间戳:

 /**     * Check resources for redeployment and reloading.     */    protected synchronized void checkResources(DeployedApplication app) {        String[] resources =            app.redeployResources.keySet().toArray(new String[0]);        for (int i = 0; i < resources.length; i++) {            File resource = new File(resources[i]);            if (log.isDebugEnabled())                log.debug("Checking context[" + app.name +                        "] redeploy resource " + resource);            long lastModified =                    app.redeployResources.get(resources[i]).longValue();            if (resource.exists() || lastModified == 0) {                if (resource.lastModified() > lastModified) {                    if (resource.isDirectory()) {                        // No action required for modified directory                        app.redeployResources.put(resources[i],                                Long.valueOf(resource.lastModified()));                    } else if (app.hasDescriptor &&                            resource.getName().toLowerCase(                                    Locale.ENGLISH).endsWith(".war")) {                        // Modified WAR triggers a reload if there is an XML                        // file present                        // The only resource that should be deleted is the                        // expanded WAR (if any)                        Context context = (Context) host.findChild(app.name);                        String docBase = context.getDocBase();                        if (!docBase.toLowerCase(Locale.ENGLISH).endsWith(".war")) {                            // This is an expanded directory                            File docBaseFile = new File(docBase);                            if (!docBaseFile.isAbsolute()) {                                docBaseFile = new File(host.getAppBaseFile(),                                        docBase);                            }                            ExpandWar.delete(docBaseFile);                            // Reset the docBase to trigger re-expansion of the                            // WAR                            context.setDocBase(resource.getAbsolutePath());                        }                        reload(app);                        // Update times                        app.redeployResources.put(resources[i],                                Long.valueOf(resource.lastModified()));                        app.timestamp = System.currentTimeMillis();                        boolean unpackWAR = unpackWARs;                        if (unpackWAR && context instanceof StandardContext) {                            unpackWAR = ((StandardContext) context).getUnpackWAR();                        }                        if (unpackWAR) {                            addWatchedResources(app, context.getDocBase(), context);                        } else {                            addWatchedResources(app, null, context);                        }                        return;                    } else {                        // Everything else triggers a redeploy                        // (just need to undeploy here, deploy will follow)                        undeploy(app);                        deleteRedeployResources(app, resources, i, false);                        return;                    }                }            } else {                // There is a chance the the resource was only missing                // temporarily eg renamed during a text editor save                try {                    Thread.sleep(500);                } catch (InterruptedException e1) {                    // Ignore                }                // Recheck the resource to see if it was really deleted                if (resource.exists()) {                    continue;                }                if (lastModified == 0L) {                    continue;                }                // Undeploy application                undeploy(app);                deleteRedeployResources(app, resources, i, true);                return;            }        }        resources = app.reloadResources.keySet().toArray(new String[0]);        boolean update = false;        for (int i = 0; i < resources.length; i++) {            File resource = new File(resources[i]);            if (log.isDebugEnabled()) {                log.debug("Checking context[" + app.name + "] reload resource " + resource);            }            long lastModified = app.reloadResources.get(resources[i]).longValue();            if (resource.lastModified() != lastModified || update) {                if (!update) {                    // Reload application                    reload(app);                    update = true;                }                // Update times. More than one file may have been updated. We                // don't want to trigger a series of reloads.                app.reloadResources.put(resources[i],                        Long.valueOf(resource.lastModified()));            }            app.timestamp = System.currentTimeMillis();        }    }

可以看到它使用的是File的lastModified()方法。

最后这个检测过程是什么时候触发呢?我们知道配置

  <Host name="localhost"  appBase="webapps" unpackWARs="true" autoDeploy="true">

Host的autoDeploy=true的时候才会自动发布,就在这里体现。

/**     * Check status of all webapps.     */    protected void check() {        if (host.getAutoDeploy()) {            // Check for resources modification to trigger redeployment            DeployedApplication[] apps =                deployed.values().toArray(new DeployedApplication[0]);            for (int i = 0; i < apps.length; i++) {                if (!isServiced(apps[i].name))                    checkResources(apps[i]);            }            // Check for old versions of applications that can now be undeployed            if (host.getUndeployOldVersions()) {                checkUndeploy();            }            // Hotdeploy applications            deployApps();        }    }

我现在告诉你这个check()是周期性执行的话,你肯定会问如何周期性执行,接着看:


可以看到一个周期性事件触发了这个过程。那么谁会周期性的去发送这个周期性事件呢?

org.apache.catalina.core.StandardEngine 就是我们Engine标签的实际代表者,这个类继承了org.apache.catalina.core.ContainerBase,其中有一段:


在容器启动的时候会创建一个守护线程去周期性的发出周期性事件


那么肯定还有最后一个问题,这个周期性事件的间隔是多少,怎么设置?

默认是10秒 如果我们想改变这个值怎么办?

打开你的server.xml 然后在Engine标签中添加属性

<Engine name="Catalina" defaultHost="localhost" backgroundProcessorDelay="20">
然后我们改动一下代码:


然后我们可以看到改变了周期性事件的间隔。

目前这个周期性事件是从engine触发的,如果我们想让其他容器触发怎么办?

对应的标签添加属性backgroundProcessorDelay。只要继承了org.apache.catalina.core.ContainerBase这个容器的对应标签都可以出发出周期性事件。

1 0