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

使用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这个容器的对应标签都可以出发出周期性事件。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值