天天看点

tomcat源码分析-Container初始化与加载

我们谈到tomcat时,第一印象是它是一种servlet容器,这个概念是相当抽象和本质的,我们仍然对tomcat的内幕很陌生。我们知道,tomcat由connector和container两大组件构成,connector在前面的文章已经介绍过了,今天我们就来看看container是怎么回事。

一、container基本结构

    前文中有讲到,connector和container的初始化工作是由digester解析conf/server.xml来完成的,而在server.xml中已经告诉了我们container的基本结构。我们先来看看server.xml文件:

tomcat源码分析-Container初始化与加载

<server port="8005" shutdown="shutdown">  

  <service name="catalina">  

    <connector port="8080" protocol="http/1.1"  

               connectiontimeout="20000"  

               redirectport="8443" />  

    <connector port="8009" protocol="ajp/1.3" redirectport="8443" />  

    <engine name="catalina" defaulthost="localhost">  

      <realm classname="org.apache.catalina.realm.lockoutrealm">  

        <realm classname="org.apache.catalina.realm.userdatabaserealm"  

               resourcename="userdatabase"/>  

      </realm>  

      <host name="localhost"  appbase="webapps"  

            unpackwars="true" autodeploy="true">  

        <valve classname="org.apache.catalina.valves.accesslogvalve" directory="logs"  

               prefix="localhost_access_log." suffix=".txt"  

               pattern="%h %l %u %t "%r" %s %b" />  

      </host>  

    </engine>  

  </service>  

</server>  

 通过xml文件,我们可以很清晰的看到,server下包含了service(可有多个),service下包含了connector和engine,其实还可以包含executor(线程池)。engine正是与connector处在同一水平的容器,engine下面有host,下面给出tomcat模块组成图。

tomcat源码分析-Container初始化与加载

前面在模拟tomcat连接器时,正是将servlet容器的实例作为参数传入到连接器的setcontainer()方法中,这样连接器才能调用servlet容器的invoke()方法。 

    接下来给出一张大众化的container组件结构图。

tomcat源码分析-Container初始化与加载

 通过代码可以知道,tomcat提供了一个container接口来抽象容器,并且细分了4种类型的容器,分别是engine、host、context和wrapper,对应不同的概念层次。

· engine:表示整个catalina的servlet引擎

· host:表示一个拥有数个上下文的虚拟主机

· context:表示一个web应用,一个context包含一个或多个wrapper

· wrapper:表示一个独立的servlet 

engine是顶层容器,java程序能够运行是因为有引擎即jvm,很自然的,engine正是可以配置jvm(jvmroute="jvm1")。host是engine的子容器,context是host的子容器,wrapper是context的子容器,这4个接口的标准实现分别是standardengine、standardhost、standardcontext、standardwrapper,她们都在org.apache.catalina.core包类,分析tomcat容器正式主要分析这4个类。下图展示了container接口及其子接口和实现类的uml类图。

tomcat源码分析-Container初始化与加载

通过上面的两个图,我们知道了container的结构,那么container的初始化工作是怎么完成的呢?

二、container初始化

    回到catalina类中,在load方法中调用了createstartdigester方法。

tomcat源码分析-Container初始化与加载

/** 

 * create and configure the digester we will be using for startup. 

 */  

protected digester createstartdigester() {  

    digester digester = new digester();  

    // initialize the digester  

    // configure the actions we will be using  

    // 如果遇到”server“元素起始符;则创建"org.apache.catalina.core.standardserv        // er"的一个实例对象,并压入堆栈;如果"server"元素的"classname"属性存在,那么用        // 这个属性的值所指定的class来创建实例对象,并压入堆栈。  

    digester.addobjectcreate("server",  

                             "org.apache.catalina.core.standardserver",  

                             "classname");  

    // 从server.xml读取"server"元素的所有{属性:值}配对,用对应的setter方法将属性值        // 设置到堆栈顶层元素(server)。  

    digester.addsetproperties("server");  

    digester.addsetnext("server",  

                        "setserver",  

                        "org.apache.catalina.server");  

    digester.addobjectcreate("server/service",  

                             "org.apache.catalina.core.standardservice",  

    digester.addsetproperties("server/service");  

    digester.addsetnext("server/service",  

                        "addservice",  

                        "org.apache.catalina.service");  

    digester.addobjectcreate("server/service/listener",  

                             null, // must be specified in the element  

    digester.addsetproperties("server/service/listener");  

    digester.addsetnext("server/service/listener",  

                        "addlifecyclelistener",  

                        "org.apache.catalina.lifecyclelistener");  

    //executor  

    //connector  

    // add rulesets for nested elements  

    digester.addruleset(new namingruleset("server/globalnamingresources/"));  

    digester.addruleset(new engineruleset("server/service/"));  

    digester.addruleset(new hostruleset("server/service/engine/"));  

    digester.addruleset(new contextruleset("server/service/engine/host/"));  

    addclusterruleset(digester, "server/service/engine/host/cluster/");  

    digester.addruleset(new namingruleset("server/service/engine/host/context/"));  

    // when the 'engine' is found, set the parentclassloader.  

    digester.addrule("server/service/engine",  

                     new setparentclassloaderrule(parentclassloader));  

    addclusterruleset(digester, "server/service/engine/cluster/");  

    return (digester);  

}  

    从简化的源码中可以看到,digester对server.xml设置的标签动作有5种调用:

    addobjectcreate:遇到起始标签的元素,初始化一个实例对象入栈

    addsetproperties:遇到某个属性名,使用setter来赋值

    addsetnext:遇到结束标签的元素,调用相应的方法

    addrule:调用rule的begin 、body、end、finish方法来解析xml,入栈和出栈给对象赋值

    addruleset:调用addruleinstances来解析xml标签

    从这些规则和xml中可以看到,calatina的server对象是standardserver。standardservice包含了多个connector(xml中有2个connector)和一个standardengine container。standardengine包含了一个host container。

三、context容器加载web服务与热部署

    从confg/server.xml中我们可以看到server的容器的初始化只有engine和host,那么context是什么时候初始化的呢,是怎么加载我们的web application,怎么实现的热部署呢?

    先说结论,tomcat的engine会启动一个线程,该线程每10s会发送一个发送一个事件,监听到该事件的部署配置类会自动去扫描webapp文件夹下的war包,将其加载成一个context,即启动一个web服务。

    ok,回过头看conf/server.xml和createstartdigester,添加了hostruleset,进入在hostruleset类中,可以看到这么一行代码:

tomcat源码分析-Container初始化与加载

digester.addrule(prefix + "host", new lifecyclelistenerrule  

              ("org.apache.catalina.startup.hostconfig", "hostconfigclass"));  

    继续进入lifecyclelistenerrule类可以发现,在监听事件中增加了hostconfig类的对象,也就是standardhost中新增了一个hostconfig监听器。再回过头来进入standardengine的starinternal方法supper.startinternal(父类containerbase)中有这行代码:

    threadstart();

进入后发现开启了一个线程,调用containerbackgroundprocessor这个的run方法,而这个run方法可以看到,

tomcat源码分析-Container初始化与加载

protected class containerbackgroundprocessor implements runnable {  

    @override  

    public void run() {  

        //  

        try {  

            while (!threaddone) {  

                try {  

                    thread.sleep(backgroundprocessordelay * 1000l);//在standardengine中构造方法设置默认backgroundprocessordelay=10,即10s调用一次  

                } catch (interruptedexception e) {  

                    // ignore  

                }  

                if (!threaddone) {  

                    //  

                    processchildren(parent, cl);  

            }  

        }   

    }  

也就是说每该线程每10s会调用一次processchildren,继续跟踪该方法,会看到调用其子容器engine、host、context、wrapper各容器组件及与它们相关的其它组件的backgroundprocess方法。

tomcat源码分析-Container初始化与加载

@override  

public void backgroundprocess() {  

    if (loader != null) {  

            loader.backgroundprocess();  

        } catch (exception e) {  

            log.warn(sm.getstring("containerbase.backgroundprocess.loader", loader), e);                  

        }  

    //  

    firelifecycleevent(lifecycle.periodic_event, null);  

这个方法中比较重要的两个

loader.backgroundprocess():调用了载入器的webapploader的backgroundprocess方法,进入这个方法可以看到:

tomcat源码分析-Container初始化与加载

    if (reloadable && modified()) {  

            thread.currentthread().setcontextclassloader  

                (webapploader.class.getclassloader());  

            if (container instanceof standardcontext) {  

                ((standardcontext) container).reload();  

        } finally {  

            if (container.getloader() != null) {  

                thread.currentthread().setcontextclassloader  

                    (container.getloader().getclassloader());  

    } else {  

        closejars(false);  

看判断条件reloadable和modified(),reloadable即为是否开启热部署,而modified()则是当前文件是否有修改的判断,当开启了热部署且有修改就会调用context的reload方法进行重加载,实现web服务的**热部署**。

firelifecycleevent:对容器的监听对象发送lifecycle.periodic_event事件,调用lifecyclelistener的lifecycleevent。

tomcat源码分析-Container初始化与加载

public void firelifecycleevent(string type, object data) {  

    lifecycleevent event = new lifecycleevent(lifecycle, type, data);  

    lifecyclelistener interested[] = listeners;  

    for (int i = 0; i < interested.length; i++)  

        interested[i].lifecycleevent(event);  

好的,前面说到standardhost通server.xml配置了hostconfig监听器,那么进入hostconfig查看对该事件的响应方法lifecycleevent

tomcat源码分析-Container初始化与加载

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

            setcontextclass(((standardhost) host).getcontextclass());  

    } catch (classcastexception e) {  

        log.error(sm.getstring("hostconfig.cce", event.getlifecycle()), e);  

        return;  

    // 看事件与其对应的方法调用  

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

可以看到lifecycle.periodic_event事件会调用其check方法。

tomcat源码分析-Container初始化与加载

protected void check() {  

    if (host.getautodeploy()) {//这个条件对应这server.xml的host配置的autodeploy="true"  

        deployedapplication[] apps =  

            deployed.values().toarray(new deployedapplication[0]);  

        for (int i = 0; i < apps.length; i++) {  

            if (!isserviced(apps[i].name))  

                //资源查找  

                checkresources(apps[i], false);  

        if (host.getundeployoldversions()) {  

            checkundeploy();  

        //部署  

        deployapps();  

很显然,如果server.xml的host配置了能够自动部署,那么会调用deployapps方法。也就是说tomcat每10s会调用一次deployapps,完**web application的部署**。

tomcat源码分析-Container初始化与加载

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

可以看到可以通过xml,war包等直接部署!

tomcat源码分析-Container初始化与加载

protected void deploydescriptor(contextname cn, file contextxml) {  

    context context = null;  

    class<?> clazz = class.forname(host.getconfigclass());//默认值:contextconfig  

    lifecyclelistener listener =  

        (lifecyclelistener) clazz.newinstance();  

    context.addlifecyclelistener(listener);  

    host.addchild(context);  

而部署的过程,其实就是创建了context对象,并添加到host中。

此外从hostconfig部署contex的方法中可以看到,有3中方式部署war包:

  1 在server.xml的host标签中声明context标签

  2 将war包放入webapps中

  3 context.xml配置方式

至此,我们已经知道了engine、host、context的加载了,同时也知道了tomcat是怎么加载我们的web服务,是怎么实现的热部署。那么接下来就剩下最后一个wrapper的加载了。

很捉急,在server.xml中没有关于wrapper的初始化加载,那么在哪里呢? 

同样回到,上面的deployapps()方法中,在其三种部署方式中都有一节代码

tomcat源码分析-Container初始化与加载

class<?> clazz = class.forname(host.getconfigclass());//默认值:contextconfig  

lifecyclelistener listener = (lifecyclelistener) clazz.newinstance();  

context.addlifecyclelistener(listener);  

这段代码的作用是给context容器添加了contextconfig监听器。而在context的startinternal方法中,发送了监听事件:

tomcat源码分析-Container初始化与加载

firelifecycleevent(lifecycle.configure_start_event, null);  

contextconfig监听到该事件,调用configurestart方法,在该方法中调用webconfig(),webconfig完成web.xml解析,生成servlet、filter等信息,并配置加载wrapper。通过对contextconfig的分析可以知道,wrapper 代表一个 servlet,它负责管理一个 servlet,包括的 servlet 的装载、初始化、执行以及资源回收。

    好了,从engine---host---contex----wrapper这个链路上的容器初始化和app加载已经完成。接下来的文章我们来看请求在容器里所走过的代码逻辑。

原文链接:[http://wely.iteye.com/blog/2295222]