zoukankan      html  css  js  c++  java
  • 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()方法来完成。

    (完)

  • 相关阅读:
    docker
    协程 gevent
    vue
    数据
    elk 配置
    iOS下架
    综合练习:词频统计
    组合数据类型综合练习
    Python基础综合练习
    熟悉常用的Linux操作
  • 原文地址:https://www.cnblogs.com/coldridgeValley/p/6203270.html
Copyright © 2011-2022 走看看