天天看點

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]