`
Tyrion
  • 浏览: 257654 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

Tomcat7中web应用加载原理(一)Context构建

阅读更多

为什么关心Tomcat中一个web应用的加载过程?在前面的文章中看过多次Tomcat的组件结构图,这里再贴出来回顾一下:


之前的《Tomcat7启动分析》系列文章中看到Tomcat启动的时候将会解析server.xml,根据里面所配置的各个节点信息逐一初始化和启动相应组件(即分别调用它们的init和start方法),但浏览一下Tomcat7源码中的server.xml的内容,里面对应上图中的已经默认配置的各级组件包括Server、Service、Engine、Connector、Host、Valve。上图中的Context组件实际就是我们通常所说的一个web应用,有趣的是在server.xml中并没有配置该组件,而我们默认启动的时候实际上已经有好几个web应用可以访问了:


这个到底是怎么回事?

 

看过前面的《Tomcat7中一次请求处理的前世今生》系列文章的人应该知道,浏览器一次请求送到Tomcat服务器之后,最终会根据浏览器中的url路径找到相应的实际要访问的web应用的context对象(默认即org.apache.catalina.core.StandardContext类的实例)。比如访问的url为http://localhost:8080/,那么将会送到上图ROOT文件夹表示的web应用中,访问的url为http://localhost:8080/docs,那么将访问docs文件夹表示的web应用。所以能够猜到的是在Tomcat启动完成后,必定容器内部已经构造好了表示相应web应用的各个context对象。

 

本文就对这个问题一探究竟。在《Tomcat7服务器关闭原理》的开头提到,默认的配置下Tomcat启动完之后会看到后台实际上总共有6个线程在运行:


前面的几篇文章中涉及了main、http-bio-8080-Acceptor-0、http-bio-8080-AsyncTimeout、ajp-bio-8009-Acceptor-0、ajp-bio-8009-AsyncTimeout,已经谈到了这些线程的作用,它们是如何产生并响应请求的。但有一个线程没有说,即ContainerBackgroundProcessor[StandardEngine[Catalina]],而本文要解答的问题奥秘就在这个线程之中。

 

先看看这个线程是如何产生的,其实从命名就可以看出一些端倪,它叫做容器后台处理器,并且跟StandardEngine关联起来,它的产生于作用也同样如此。

Tomcat7中所有的默认容器组件(StandardEngine、StandardHost、StandardContext、StandardWrapper)都会继承父类org.apache.catalina.core.ContainerBase,在这些容器组件启动时将会调用自己内部的startInternal方法,在该方法内部一般会调用父类的startInternal方法(StandardContext类的实现除外),比如org.apache.catalina.core.StandardEngine类中的startInternal方法:

    @Override
    protected synchronized void startInternal() throws LifecycleException {
        
        // Log our server identification information
        if(log.isInfoEnabled())
            log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());

        // Standard container startup
        super.startInternal();
    }

最后的super.startInternal()即调用父类org.apache.catalina.core.ContainerBase的startInternal方法,在该方法最后:

        // Start the Valves in our pipeline (including the basic), if any
        if (pipeline instanceof Lifecycle)
            ((Lifecycle) pipeline).start();


        setState(LifecycleState.STARTING);

        // Start our thread
        threadStart();

第6行设置了LifecycleState.STARTING状态(这样将向容器发布一个Lifecycle.START_EVENT事件),这一行的作用本文后面会提到,暂且按下不表。第9行调用threadStart方法,看看threadStart方法的代码:

    // -------------------- Background Thread --------------------

    /**
     * Start the background thread that will periodically check for
     * session timeouts.
     */
    protected void threadStart() {

        if (thread != null)
            return;
        if (backgroundProcessorDelay <= 0)
            return;

        threadDone = false;
        String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
        thread = new Thread(new ContainerBackgroundProcessor(), threadName);
        thread.setDaemon(true);
        thread.start();

    }

这里可以看到如果两个前置校验条件通过的话将会启动一个线程,并且线程的名字即以" ContainerBackgroundProcessor[ "开头,线程名字后面取的是对象的toString方法,以StandardEngine为例,看看org.apache.catalina.core.StandardEngine的toString方法实现:

    /**
     * Return a String representation of this component.
     */
    @Override
    public String toString() {

        StringBuilder sb = new StringBuilder("StandardEngine[");
        sb.append(getName());
        sb.append("]");
        return (sb.toString());

    }

以上解释了这个后台线程的来历。

 

但这里有一个问题,既然StandardEngine、StandardHost都会调用super.startInternal()方法,按默认配置,后台理应产生两个后台线程,实际为什么只有一个?

回到org.apache.catalina.core.ContainerBase的threadStart方法,在启动线程代码之前有两个校验条件:

        if (thread != null)
            return;
        if (backgroundProcessorDelay <= 0)
            return;

容器组件对象初始化时thread为null,backgroundProcessorDelay是-1

    /**
     * The background thread.
     */
    private Thread thread = null;

    /**
     * The processor delay for this component.
     */
    protected int backgroundProcessorDelay = -1;

但org.apache.catalina.core.StandardEngine在其自身构造函数中做了一点修改:

    public StandardEngine() {

        super();
        pipeline.setBasic(new StandardEngineValve());
        /* Set the jmvRoute using the system property jvmRoute */
        try {
            setJvmRoute(System.getProperty("jvmRoute"));
        } catch(Exception ex) {
            log.warn(sm.getString("standardEngine.jvmRouteFail"));
        }
        // By default, the engine will hold the reloading thread
        backgroundProcessorDelay = 10;

    }

构造函数最后将父类的backgroundProcessorDelay的值由-1改成了10,所以Tomcat启动解析xml时碰到一个Engine节点就会对应产生一个后台处理线程。

 

讲完了这个后台处理线程的产生,看看这个线程所作的事情,再看下这个线程的启动代码:

        thread = new Thread(new ContainerBackgroundProcessor(), threadName);
        thread.setDaemon(true);
        thread.start();

所以这个线程将会执行ContainerBase的内部类ContainerBackgroundProcessor的run方法,看下ContainerBackgroundProcessor的全部实现代码:

    /**
     * Private thread class to invoke the backgroundProcess method 
     * of this container and its children after a fixed delay.
     */
    protected class ContainerBackgroundProcessor implements Runnable {

        @Override
        public void run() {
            while (!threadDone) {
                try {
                    Thread.sleep(backgroundProcessorDelay * 1000L);
                } catch (InterruptedException e) {
                    // Ignore
                }
                if (!threadDone) {
                    Container parent = (Container) getMappingObject();
                    ClassLoader cl = 
                        Thread.currentThread().getContextClassLoader();
                    if (parent.getLoader() != null) {
                        cl = parent.getLoader().getClassLoader();
                    }
                    processChildren(parent, cl);
                }
            }
        }

        protected void processChildren(Container container, ClassLoader cl) {
            try {
                if (container.getLoader() != null) {
                    Thread.currentThread().setContextClassLoader
                        (container.getLoader().getClassLoader());
                }
                container.backgroundProcess();
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                log.error("Exception invoking periodic operation: ", t);
            } finally {
                Thread.currentThread().setContextClassLoader(cl);
            }
            Container[] children = container.findChildren();
            for (int i = 0; i < children.length; i++) {
                if (children[i].getBackgroundProcessorDelay() <= 0) {
                    processChildren(children[i], cl);
                }
            }
        }
    }

在它的run方法暂停一段时间之后会调用processChildren方法,而processChildren方法做了两件事,一是调用容器组件自身的backgroundProcess方法,而是取出该容器组件的所有子容器组件并调用它们的processChildren方法。归结起来这个线程的实现就是定期通过递归的方式调用当前容器及其所有子容器的backgroundProcess方法。

而这个backgroundProcess方法在ContainerBase内部已经给出了实现:

    @Override
    public void backgroundProcess() {
        
        if (!getState().isAvailable())
            return;

        if (cluster != null) {
            try {
                cluster.backgroundProcess();
            } catch (Exception e) {
                log.warn(sm.getString("containerBase.backgroundProcess.cluster", cluster), e);                
            }
        }
        if (loader != null) {
            try {
                loader.backgroundProcess();
            } catch (Exception e) {
                log.warn(sm.getString("containerBase.backgroundProcess.loader", loader), e);                
            }
        }
        if (manager != null) {
            try {
                manager.backgroundProcess();
            } catch (Exception e) {
                log.warn(sm.getString("containerBase.backgroundProcess.manager", manager), e);                
            }
        }
        Realm realm = getRealmInternal();
        if (realm != null) {
            try {
                realm.backgroundProcess();
            } catch (Exception e) {
                log.warn(sm.getString("containerBase.backgroundProcess.realm", realm), e);                
            }
        }
        Valve current = pipeline.getFirst();
        while (current != null) {
            try {
                current.backgroundProcess();
            } catch (Exception e) {
                log.warn(sm.getString("containerBase.backgroundProcess.valve", current), e);                
            }
            current = current.getNext();
        }
        fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);
    }

这段代码就不一一解释了,概括起来说就是逐个调用与容器相关其它内部组件的backgroundProcess方法。最后注册一个Lifecycle.PERIODIC_EVENT事件。

 

上面就是Tomcat7的后台处理线程所作的事情的概述,在Tomcat的早期版本中有一些后台处理的事情原来是在各个组件内部分别自定义一个线程并启动,在Tomcat5中改成了所有后台处理共享同一线程的方式。

 

回到本文要解答的问题,web应用如何加载到容器中的?在ContainerBase类的backgroundProcess方法的最后:

        fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);

向容器注册了一个PERIODIC_EVENT事件。前面说道默认的ContainerBackgroundProcessor[StandardEngine[Catalina]]线程会定期(默认为10秒)执行Engine、Host、Context、Wrapper各容器组件及与它们相关的其它组件的backgroundProcess方法,所以也会定期向Host组件发布一个PERIODIC_EVENT事件,这里看下StandardHost都会关联的一个监听器org.apache.catalina.startup.HostConfig(

在Tomcat启动解析xml时org.apache.catalina.startup.Catalina类的386行:digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"))

在HostRuleSet类的addRuleInstances方法中:

    public void addRuleInstances(Digester digester) {

        digester.addObjectCreate(prefix + "Host",
                                 "org.apache.catalina.core.StandardHost",
                                 "className");
        digester.addSetProperties(prefix + "Host");
        digester.addRule(prefix + "Host",
                         new CopyParentClassLoaderRule());
        digester.addRule(prefix + "Host",
                         new LifecycleListenerRule
                         ("org.apache.catalina.startup.HostConfig",
                          "hostConfigClass"));
        digester.addSetNext(prefix + "Host",
                            "addChild",
                            "org.apache.catalina.Container");

第9到12行看到,所有Host节点都会添加一个org.apache.catalina.startup.HostConfig对象作为org.apache.catalina.core.StandardHost对象的监听器

),在HostConfig的lifecycleEvent方法中可以看到如果Host组件收到了Lifecycle.PERIODIC_EVENT事件的发布所作出的响应(如果对Tomcat7的Lifecycle机制不清楚可以看下《Tomcat7启动分析(五)Lifecycle机制和实现原理》):

    public void lifecycleEvent(LifecycleEvent event) {

        // Identify the host we are associated with
        try {
            host = (Host) event.getLifecycle();
            if (host instanceof StandardHost) {
                setCopyXML(((StandardHost) host).isCopyXML());
                setDeployXML(((StandardHost) host).isDeployXML());
                setUnpackWARs(((StandardHost) host).isUnpackWARs());
            }
        } catch (ClassCastException e) {
            log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
            return;
        }

        // Process the event that has occurred
        if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
            check();
        } else if (event.getType().equals(Lifecycle.START_EVENT)) {
            start();
        } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
            stop();
        }
    }

第17行,如果发布的事件是PERIODIC_EVENT将会执行check方法。第19行,如果发布的事件是START_EVENT则执行start方法。check方法和start方法最后都会调用deployApps()方法,看下这方法的实现:

    /**
     * Deploy applications for any directories or WAR files that are found
     * in our "application root" directory.
     */
    protected void deployApps() {

        File appBase = appBase();
        File configBase = configBase();
        String[] filteredAppPaths = filterAppPaths(appBase.list());
        // Deploy XML descriptors from configBase
        deployDescriptors(configBase, configBase.list());
        // Deploy WARs
        deployWARs(appBase, filteredAppPaths);
        // Deploy expanded folders
        deployDirectories(appBase, filteredAppPaths);

    }

这里即各种不同方式发布web应用的代码。

 

本文前面提到默认情况下组件启动的时候会发布一个Lifecycle.START_EVENT事件(在org.apache.catalina.core.ContainerBase类的startInternal方法倒数第二行),回到HostConfig的lifecycleEvent方法中,所以默认启动时将会执行HostConfig的start方法,在该方法的最后:

        if (host.getDeployOnStartup())
            deployApps();

因为默认配置host.getDeployOnStartup()返回true,这样容器就会在启动的时候直接加载相应的web应用。

 

当然,如果在server.xml中Host节点的deployOnStartup属性设置为false,则容器启动时不会加载应用,启动完之后不能立即提供web应用的服务。但因为有上面提到的后台处理线程在运行,会定期执行HostConfig的check方法:

    /**
     * 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();
        }
    }

如果Host节点的autoDeploy属性是true(默认设置即为true),可以看到check方法最后同样会加载web应用。

  • 大小: 76.4 KB
  • 大小: 6.4 KB
  • 大小: 39.5 KB
3
2
分享到:
评论
1 楼 akka_li 2016-04-03  
好不容易找到讲解servlet容器启动过程、解析web.xml文件的文章,太激动了!!!

相关推荐

    Tomcat中用web.xml控制Web应用详解

    Tomcat中用web.xml控制Web应用详解

    精通Tomcat-Java Web应用开发

    精通Tomcat-Java Web应用开发 pdf 版本 超清晰 完整版本!

    如何在Tomcat 7.0服务器中添加Web应用及注意事项

    如何在Tomcat 7.0服务器中添加Web应用及注意事项: 本文档记录了如何在Tomcat 7.0(6.0)中添加Web应用以及使用技巧,没有技术含量,纯属实用技术。 1、在apache-tomcat-7.0.30\conf\server.xml文件中添加…… 2...

    Tomcat与Java Web开发技术详解 孙卫琴 源码

    由于Java Web技术是SUN公司在Java Servlet规范中提出的通用技术,因此《Tomcat与Java Web开发技术详解》讲解的Java Web应用例子可以运行在任何一个实现了SUN的Servlet规范的Java Web服务器上。随书附赠光盘的内容为...

    Tomcat6.0 web服务器

    Tomcat是目前比较流行的Web 应用服务器。 有两个办法可以在系统...这种context片断提供了一种便利的方法来部署web应用,不需要编辑server.xml,除非想改变缺省的部署特性,安装一个新的web应用时不需要重启动Tomcat。

    Tomcat 与Java web开发技术详解(孙卫琴)

    由于Java Web技术是SUN公司在Java Servlet规范中提出的通用技术,因此本书讲解的Java Web应用例子可以运行在任何一个实现了SUN的Servlet规范的Java Web服务器上。随书附赠光盘的内容为本书范例的源程序,以及本书...

    精通Tomcat:Java Web应用开发,系统集成案例实战

    精通Tomcat:Java Web应用开发、框架分析与组件配置、系统集成与案例实战 -------java爱好者,java交流群:166256747,分享自己的技术是一种美德!

    tomcat7 web应用开发服务器,是一款不错的资源

    apache tomcat7 服务器,web开发工具,是一款不错的应用开发服务器

    tomcat6发布web应用及数据源的配置.

    tomcat6发布web应用及数据源的配置.rartomcat6发布web应用及数据源的配置.rartomcat6发布web应用及数据源的配置.rartomcat6发布web应用及数据源的配置.rartomcat6发布web应用及数据源的配置.rar

    Tomcat7配置详解中文文档

    介绍了Servlet 规范中web应用程序(web application)的概念,web application 的组织,文档机构,部署描述文件 (/WEB-INF/web.xml). 等 4) Deployer 介绍了如何部署web application,预编译,和验证web ...

    tomcat与java web源代码

    由于Java Web技术是SUN公司在Java Servlet规范中提出的通用技术,因此本书讲解的Java Web应用例子可以运行在任何一个实现SUN的Servlet规范的Java Web服务器上。随书附赠光盘内容为本书所有范例源程序,以及本书涉及...

    Tomcat与Java Web

    Tomcat与Java Web 代码

    Tomcat与Java Web开发技术详解(第2版)及其源码

    《Tomcat与Java Web开发技术详解》语言深入浅出、通俗易懂,无论对于Java Web开发的新手还是行家来说,《Tomcat与Java Web开发技术详解》都是精通Tomcat和开发Java Web应用的必备的实用手册。《Tomcat与Java Web开发...

    在Tomcat中部署JavaWeb应用

    详细地说明在Tomcat中部署JavaWeb应用

    tomcat5.5流行的Web 应用服务器

    Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成。由于有了Sun 的参与和支持,最新的Servlet 和JSP 规范总是能在Tomcat ...

    Tomcat-企业级web应用实战

    Tomcat 和nginx 、apache、lighttpd等web服务器一样,具有处理HTML静态文件的功能,另外还有处理并发及Servlet和JSP容器,独立的Servlet容器是tomcat默认的模式,tomcat对于静态文静的处理不如nginx、apache。

    Tomcat web应用中配置连接池的详细过程

    Tomcat web应用中配置连接池的详细过程 这里以oracle数据库连接为例,如用到其他的其它数据库如 mysql sqlserver db2 等只需在相应的位置配置各数据库的驱动名称和对应的url 用户名 密码

    tomcat8.0 服务器是一个免费的开放源代码的Web 应用服务器

    Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选。对于一个初学者来说,可以这样认为,当在一台...

    Tomcat7 启动类加载日志

    Tomcat7.0.62 启动类加载日志

    Tomcat与Java_Web开发技术详解3.pdf

    本书详细介绍了Tomcat 5 版本上开发 Java Web 应用的各种技术。主要包括:Tomcat和Java Web开发的基础知识,Java Web开发的高级技术,Tomcat与当前其他通用软件的集成,以及Tomcat的各种高级功能。 书中内容主要...

Global site tag (gtag.js) - Google Analytics