Tomcat源码分析之HostConfig.start方法解析

 

        HostConfig是standedHost的生命周期状态的监听类,主要是根据监听状态进行相应的操作。今天我们主要讲start状态下,hostConfig的解析过程,大概的解析过程如下:

        

      1.获取到start监听。lifecycleEvent(LifecycleEvent event)是具体的状态处理方法,在里面根据状态判断走具体的步骤,当状态为start时,将会调用start()方法。代码如下:

public void lifecycleEvent(LifecycleEvent event) {
    ........
    // Process the event that has occurred
    if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
        check();
    } else if (event.getType().equals(Lifecycle.START_EVENT)) {
        //当状态为start时,调用start方法
        start();
    } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
        stop();
    }
}

        2.进入start方法后,首先创建appBase、xmlBase目录,如果存在就不会进行创建,其次判断host.getAppBaseFile()是否为目录,如果是非目录将会设置DeployOnStartup和AutoDeploy为false。最后判断DeployOnStartup是否为true,如果为true将会deployApps()方法发布应用。代码如下:

public void start() {
    ..........
    if (host.getCreateDirs()) {//判断是否需要创建appBase(webapps绝对路径)、configBase(conf\Catalina\localhost的绝对路径)目录
        File[] dirs = new File[] {host.getAppBaseFile(),host.getConfigBaseFile()};
        for (int i=0; i<dirs.length; i++) {
            if (!dirs[i].mkdirs() && !dirs[i].isDirectory()) {
                log.error(sm.getString("hostConfig.createDirs",dirs[i]));
            }
        }
    }
    if (!host.getAppBaseFile().isDirectory()) {//如果webapps不是目录,将会设置DeployOnStartup和AutoDeploy为false
        log.error(sm.getString("hostConfig.appBase", host.getName(),
                host.getAppBaseFile().getPath()));
        host.setDeployOnStartup(false);
        host.setAutoDeploy(false);
    }
    //判断DeployOnStartup是否为true,如果为true将会deployApps()方法发布应用
    if (host.getDeployOnStartup())
        deployApps();
}

3.进入deployApps方法,将会对Descriptors(描述符,catalina/localhost/下的xml文件解析及发布)、WARs(webapps下的war包)、directories(webapps下的目录应用)进行发布。代码如下:

protected void deployApps() {
    File appBase = host.getAppBaseFile();
    File configBase = host.getConfigBaseFile();
    String[] filteredAppPaths = filterAppPaths(appBase.list());
    // 加载并解析catalina/localhost/目录下的xxx.xml文件,读取Context标签(<Context path="/helloapp" docBase="C:\ chapter03\helloapp" reloadable="true"/>)
    //加载docBase目录下,作为应用,如果是目录
    deployDescriptors(configBase, configBase.list());
    // Deploy webapps下的WARs
    deployWARs(appBase, filteredAppPaths);
    // Deploy webapps下的folders
    deployDirectories(appBase, filteredAppPaths);
}

4.Descriptors(描述符)发布

4.1进入deployDescriptors()方法,在方法中将catalina/localhost/下的context文件,生成file对象,采用ExecutorService异步执行发布context任务,代码如下:

protected void deployDescriptors(File configBase, String[] files) {
    ExecutorService es = host.getStartStopExecutor();
    List<Future<?>> results = new ArrayList<>();
    for (int i = 0; i < files.length; i++) {
        File contextXml = new File(configBase, files[i]);
        if (files[i].toLowerCase(Locale.ENGLISH).endsWith(".xml")) {
            ContextName cn = new ContextName(files[i]);
            if (isServiced(cn.getName()) || deploymentExists(cn.getName()))
                continue;
                //封装成DeployDescriptor对象,最后执行run方法
            results.add(es.submit(new DeployDescriptor(this, cn, contextXml)));
        }
        }
    for (Future<?> result : results) {
        try {
            //将会调用DeployDescriptor类的,run方法
            result.get();
        } catch (Exception e) {
            log.error(sm.getString("hostConfig.deployDescriptor.threaded.error"), e);
        }
    }
}

4.2进入DeployDescriptor对象的run方法,在run方法里面会调用HostConfig的deployDescriptor方法,代码如下:

private static class DeployDescriptor implements Runnable {

    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);
    }
}

4.3进入config.deployDescriptor方法,在方法中,将会解析xml文件,生成standedContext、通过反射生成监听类ContextConfig对象,并把standedContext对象添加成StandHost对象的子对象。代码如下:

try (FileInputStream fis = new FileInputStream(contextXml)) {
    synchronized (digesterLock) {
        try {
            //使用digester解析xml文件,生成standedContext对象。
            context = (Context) digester.parse(fis);
        } catch (Exception e) {
            log.error(sm.getString("hostConfig.deployDescriptor.error",contextXml.getAbsolutePath()), e);
        } finally {
            if (context == null) {
                context = new FailedContext();
            }
            digester.reset();
        }
    }
    //使用反射生成生成standedContext对象的生命周期状态监听类ContextConfig
    Class<?> clazz = Class.forName(host.getConfigClass());
    LifecycleListener listener =
        (LifecycleListener) clazz.newInstance();
    //设置contexty的监听对象、配置文件地址、name、path及版本等
    context.addLifecycleListener(listener);
    context.setConfigFile(contextXml.toURI().toURL());
    context.setName(cn.getName());
    context.setPath(cn.getPath());
    context.setWebappVersion(cn.getVersion());
    //判断context指定的应用目录是否为空,如果不为空,判断它是目录还是war,如果是war是内部的还是外部的
    if (context.getDocBase() != null) {
        File docBase = new File(context.getDocBase());
        if (!docBase.isAbsolute()) {
            docBase = new File(host.getAppBaseFile(), context.getDocBase());
        }
        // 如果是外部的目录
        if (!docBase.getCanonicalPath().startsWith(
                host.getAppBaseFile().getAbsolutePath() + File.separator)) {
            isExternal = true;
            deployedApp.redeployResources.put(
                    contextXml.getAbsolutePath(),
                    Long.valueOf(contextXml.lastModified()));
            deployedApp.redeployResources.put(docBase.getAbsolutePath(),
                    Long.valueOf(docBase.lastModified()));
            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);
        }
    }
    host.addChild(context);

在host.addChild添加过程中在里面最终会调用上层容器ContainerBase的addChildInternal,在该方法中,会调standedContext的start方法。代码如下:

private void addChildInternal(Container child) {

    if( log.isDebugEnabled() )
        log.debug("Add child " + child + " " + this);
    synchronized(children) {
        if (children.get(child.getName()) != null)
            throw new IllegalArgumentException("addChild:  Child name '" +
                                               child.getName() +
                                               "' is not unique");
        child.setParent(this);  // May throw IAE
        children.put(child.getName(), child);
    }
    if ((getState().isAvailable() ||
            LifecycleState.STARTING_PREP.equals(getState())) &&
            startChildren) {
        try {
            child.start();
        } catch (LifecycleException e) {
            log.error("ContainerBase.addChild: start: ", e);
            throw new IllegalStateException
                ("ContainerBase.addChild: start: " + e);
        }
    }
    fireContainerEvent(ADD_CHILD_EVENT, child);
}

在start方法中会进行standedContext生命周期函数的初始和start操作,在执行这些生命周期函数时,会触发监听事件,让ContextConfig执行初始化和start操作,里面包含war的解压及jar的加载、生成Wrappe,生成拦截器链等(具体代码分析在ContextConfig章节讲解)。

5.deployWARs发布

主要是对webapps目录下的war包进行发布。

5.1进入deployWARs()方法,对war包进行检查后判断,然后采用ExecutorService异步执行发布context任务,代码如下:


        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);
    }
}

5.2进入DeployWar的run方法,在该方法中将会调用HostConfig.deployWAR方法,在该方法中根据不同情况生成StandContext对象:1).查找war包中或war包目录下是否存在/META-INF/context.xml,如果存在,就根据该配置文件生成standContext对象;2).不存在,反射org.apache.catalina.core.StandardContext生成对象;3).通过反射生成StandedContext的生命周期对象ContextConfig对象。

并把standedContext对象添加成StandHost对象的子对象,代码如下:

protected void deployWAR(ContextName cn, File war) {
    JarFile jar = null;
    InputStream istream = null;
    FileOutputStream fos = null;
    BufferedOutputStream ostream = null;
    File xml = new File(host.getAppBaseFile(),
            cn.getBaseName() + "/META-INF/context.xml");
    boolean xmlInWar = false;
    JarEntry entry = null;
    try {
        jar = new JarFile(war);
        entry = jar.getJarEntry(Constants.ApplicationContextXml);
        if (entry != null) {
            xmlInWar = true;
        }
    } catch (IOException e) {
        /* Ignore */
    } finally {
        /* Ignore */
    }
    Context context = null;
    try {
        if (deployXML && xml.exists() && !copyXML) {
            synchronized (digesterLock) {
                try {
                    context = (Context) digester.parse(xml);
                } catch (Exception e) {
                    log.error(sm.getString(
                            "hostConfig.deployDescriptor.error",
                            war.getAbsolutePath()), e);
                } finally {
                   /* Ignore */
                }
            }
            context.setConfigFile(xml.toURI().toURL());
        } else if (deployXML && xmlInWar) {
            synchronized (digesterLock) {
                try {
                    jar = new JarFile(war);
                    entry =
                        jar.getJarEntry(Constants.ApplicationContextXml);
                    istream = jar.getInputStream(entry);
                    context = (Context) digester.parse(istream);
                } catch (Exception e) {
                    log.error(sm.getString(
                            "hostConfig.deployDescriptor.error",
                            war.getAbsolutePath()), e);
                } finally {
                   /* Ignore */
            }
        } else if (!deployXML && xmlInWar) {
            // Block deployment as META-INF/context.xml may contain security configuration necessary for a secure deployment.
            log.error(sm.getString("hostConfig.deployDescriptor.blocked",
                    cn.getPath(), Constants.ApplicationContextXml,
                    new File(host.getConfigBaseFile(), cn.getBaseName() + ".xml")));
        } else {
            context = (Context) Class.forName(contextClass).newInstance();
        }
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
        log.error(sm.getString("hostConfig.deployWar.error",
                war.getAbsolutePath()), t);
    } finally {
        if (context == null) {
            context = new FailedContext();
        }
    }
    boolean copyThisXml = false;
    if (deployXML) {
        if (host instanceof StandardHost) {
            copyThisXml = ((StandardHost) host).isCopyXML();
        }
        // If Host is using default value Context can override it.
        if (!copyThisXml && context instanceof StandardContext) {
            copyThisXml = ((StandardContext) context).getCopyXML();
        }
        if (xmlInWar && copyThisXml) {
            // Change location of XML file to config base
            xml = new File(host.getConfigBaseFile(),
                    cn.getBaseName() + ".xml");
            entry = null;
            try {
                jar = new JarFile(war);
                entry =
                    jar.getJarEntry(Constants.ApplicationContextXml);
                istream = jar.getInputStream(entry);
                fos = new FileOutputStream(xml);
                ostream = new BufferedOutputStream(fos, 1024);
                byte buffer[] = new byte[1024];
                while (true) {
                    int n = istream.read(buffer);
                    if (n < 0) {
                        break;
                    }
                    ostream.write(buffer, 0, n);
                }
                ostream.flush();
            } catch (IOException e) {
                /* Ignore */
            } finally {
                /* Ignore */
            }
        }
    }
    DeployedApplication deployedApp = new DeployedApplication(cn.getName(),
            xml.exists() && deployXML && copyThisXml);
    // Deploy the application in this WAR file
    if(log.isInfoEnabled())
        log.info(sm.getString("hostConfig.deployWar",
                war.getAbsolutePath()));
    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 if (!copyThisXml ) {
            // In case an XML file is added to the config base later
            deployedApp.redeployResources.put(
                    (new File(host.getConfigBaseFile(),
                            cn.getBaseName() + ".xml")).getAbsolutePath(),
                    Long.valueOf(0));
        }
        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);

5.3.host.addChild(context)的操作和4.3的是一样的。

 

6.deployWdeployDirectories发布

6.1进入deployWdeployDirectories()方法,对路径进行是否为目录进行判断,然后采用ExecutorService异步执行发布context任务,代码如下:

protected void deployDirectories(File appBase, String[] files) {
    if (files == null)
        return;
    ExecutorService es = host.getStartStopExecutor();
    List<Future<?>> results = new ArrayList<>();
    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]);
            if (isServiced(cn.getName()) || deploymentExists(cn.getName()))
                continue;
            results.add(es.submit(new DeployDirectory(this, cn, dir)));
        }
    }
    for (Future<?> result : results) {
        try {
            result.get();
        } catch (Exception e) {
            ......
        }
    }
}

6.2进入DeployDirectory的run方法,将会读取子目录下的META-INF/context.xml,生成standedContext对象,通过反射生成StandedContext的生命周期对象ContextConfig对象并把standedContext对象添加成StandHost对象的子对象,代码如下:

protected void deployDirectory(ContextName cn, File dir) {
    if( log.isInfoEnabled() )
        log.info(sm.getString("hostConfig.deployDir",
                dir.getAbsolutePath()));
    Context context = null;
    File xml = new File(dir, Constants.ApplicationContextXml);
    File xmlCopy =
            new File(host.getConfigBaseFile(), cn.getBaseName() + ".xml");
    DeployedApplication deployedApp;
    boolean copyThisXml = copyXML;

    try {
        if (deployXML && xml.exists()) {
            synchronized (digesterLock) {
                try {
                    context = (Context) digester.parse(xml);
                } catch (Exception e) {
                    log.error(sm.getString(
                            "hostConfig.deployDescriptor.error",
                            xml), e);
                    context = new FailedContext();
                } finally {
                    if (context == null) {
                        context = new FailedContext();
                    }
                    digester.reset();
                }
            }
            if (copyThisXml == false && context instanceof StandardContext) {
                copyThisXml = ((StandardContext) context).getCopyXML();
            }
            if (copyThisXml) {
                InputStream is = null;
                OutputStream os = null;
                try {
                    is = new FileInputStream(xml);
                    os = new FileOutputStream(xmlCopy);
                    IOTools.flow(is, os);
                } finally {
                    // Ignore
                }
                context.setConfigFile(xmlCopy.toURI().toURL());
            } else {
                context.setConfigFile(xml.toURI().toURL());
            }
        } else if (!deployXML && xml.exists()) {
            log.error(sm.getString("hostConfig.deployDescriptor.blocked",
                    cn.getPath(), xml, xmlCopy));
            context = new FailedContext();
        } else {
            context = (Context) Class.forName(contextClass).newInstance();
        }
        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);

6.3.host.addChild(context)的操作和4.3的是一样的。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值