天天看點

深入Jetty源碼之ContextHandler

contexthandler繼承自scopedhandler,它是jetty中實作對一個web application的各種資源進行管理,并串聯實作整個servlet架構的類,比如它部分實作了servletcontext接口,并且在其doscope方法中為目前request的執行提供了相應的環境,如設定servletpath、pathinfo、設定servletcontext到threadlocal中。在jetty中,servlet的執行流程和架構由servlethandler實作,security架構由securityhandler完成,而contexthandler主要用于實作環境的配置,如request狀态的設定、classloader的配置、servletcontext的實作等。在contexthandler類中包含了一個context的内部類,用于實作servletcontext,而contexthandler中的很多字段和方法也是用于實作servletcontext,不細述。

contexthandler中dostart方法實作:

1. 使用contextpath或displayname初始化logger字段;并設定目前線程的contextclassloader為配置的classloader執行個體;初始化mimetype字段;設定context的threadlocal變量。

2. 初始化managedattributes map,并生成addbean事件;如果存在errorhandler,start它;生成contextinitialized事件。

3. 初始化availability字段。

4. 還原context的threadlocal變量和contextclassloader回原有執行個體。

contexthandler中dostop方法實作:

1. 設定availability字段為stopped狀态;初始化context的threadlocal變量和contextclassloader為目前context執行個體以及設定的classloader。

2. 生成contextdestroyed事件,以及對managedattributes,觸發removebean事件。

3. 還原context的threadlocal變量和contextclassloader回原有執行個體。

contexthandler中doscope方法實作:

1. 對request、async的dispatchertype,并且是第一次進入該contexthandler,則如果compactpath為true,compact傳入的path,即把"//", "///"等替換為"/"。

2. 對目前請求做初步檢查以決定是否需要繼續執行該請求(傳回false表示不需要繼續執行該請求):

    a. 檢查availability狀态,對unavailable,發送503 service unavailable響應;對stopped、shutdown狀态,或request的handled字段為true,傳回false。

    b. 對設定了vhosts,檢查請求消息中的servername請求頭是否和vhosts中的某個vhost相同或比配,如果不成立,則傳回false。

    c. 檢查設定的connectors數組,如果目前httpconnection中的connector.name不包含在這個設定的connectors數組中,傳回false。

    d. 檢查contextpath,如果target不以這個contextpath開頭或者在target中contextpath之後的字元不是"/",傳回false;如果allownullpathinfo設定為false,且target不以"/"結尾,發送"target + /"的重定向請求,傳回false。

    e. 對其他情況,傳回true,表示請求可以繼續處理。

3. 計算pathinfo以及target為contextpath之後的路徑,并設定contextclassloader為目前設定的classloader。

4. 保留目前request的contextpath、servletpath、pathinfo資訊。

5. 對任意非include的dispatchertype,設定request的contextpath、servletpath為null、pathinfo為傳入的target中contextpath之後的路徑。

6. 執行nextscope的邏輯。

7. 還原目前request的contextpath、servletpath、pathinfo的資訊。

contexthandler中dohandle方法實作:

1. 對有新更新context的request執行個體,向目前request添加注冊的servletrequestattributelistener,如果注冊了servletrequestlistener,生成requestinitialized事件。

2. 對request類型的dispatchertype,如果該target為保護資源(isproctedtarget,如web-inf、meta-inf目錄下的檔案),抛出404 not found的httpexception。

3. 執行nexthandle()邏輯。

4. 如果注冊了servletrequestlistener,生成requestdestroyed事件,并從request中移除目前contexthandler中添加的servletrequestattributelistener執行個體。

servletcontexthandler繼承自contexthandler類,它串連了sessionhandler、securityhandler和servlethandler,在servletcontexthandler的start過程中,它會串連如下handler:

servletcontexthandler -....->sessionhandler->securityhandler->servlethandler,由于servletcontexthandler、sessionhandler、servlethandler都繼承自scopedhandler,因而他們的執行棧将會是:

|->servletcontexthandler.doscope()

  |-> sessionhandler.doscope()

    |-> servlethandler.doscope()

      |-> servletcontexthandler.dohandle()

        |-> .....handler.handle()

          |-> sessionhandler.dohandle()

            |-> securityhandler.handle()

              |-> servlethandler.dohandle()

另外servletcontexthandler還提供了一個decorator的擴充點,可以向servletcontexthandler注冊多個decorator,在servletcontexthandler啟動時,它會對每個已注冊的servletholder和filterholder執行一些額外的“裝飾”邏輯,出了對servletholder和filterholder的裝飾,它還可以裝飾filter、servlet、listener等,以及在銷毀他們時加入一下自定義的邏輯:

    public interface decorator {

        <t extends filter> t decoratefilterinstance(t filter) throws servletexception;

        <t extends servlet> t decorateservletinstance(t servlet) throws servletexception;

        <t extends eventlistener> t decoratelistenerinstance(t listener) throws servletexception;

        void decoratefilterholder(filterholder filter) throws servletexception;

        void decorateservletholder(servletholder servlet) throws servletexception;

        void destroyservletinstance(servlet s);

        void destroyfilterinstance(filter f);

        void destroylistenerinstance(eventlistener f);

    }

decorator擴充點的引入實作了兩種方式對servlet、filter、eventlistener的配置:annotation方式(annotationdecorator)和plus方式(plusdecorator),其中annotation的方式的配置是servlet 3.0規範中新加入的特性,而plus方式則是jetty提供的配置注入。

其中annotationdecorator的實作采用annotationinstrospector,可以向它注冊不同的instrospectableannotationhandler,用以處理不同的annotation邏輯,進而實作對動态注冊的servlet、filter、eventlistener,可以使用在它們之上的annotation來做進一步的配置,以簡化配置本身。在jetty中實作了以下幾個annotation的instrospectableannotationhandler:

@resource          => resourceannotationhandler: 對類上的@resource注解,将它作為一種資源綁定到目前context或server中,對field或method的@resource注解,建立一個injection執行個體放入context的attribute中。在plusdecorator中會對注冊的injection執行個體做inject操作。

@resources         => resourcesannotationhandler: 對類上的@resources注解中的每個@resource注解作為一種資源綁定到目前context或server中。

@runas              => runasannotationhandler: 關聯servlet上@runas注解的值到該servletholder中。

@servletsecurity => securityannotationhandler: 為@servletsecurity注解的servlet配置dataconstraint、roles、methodomission等。

@postconstruct    => postconstructannotationhandler: 将有該注解的方法注冊postconstructcallback回調類,在plusdecorator中的decorate方法中會調用該callback。

@predestroy        => predestroyannotationhandler: 将有該注解的方法注冊predestroycallback回調類,在plusdecorator中的decorate方法中會調用該callback。

@multipartconfig  => multipartconfigannotationhandler: 将有該注解的servlet類注冊配置的multipartconfig資訊。

@declareroles     => declarerolesannotationhandler: 向securityhandler注冊定義的role集合。

而plusdecorator主要處理使用以上annotation或plusdescriptorprocessor注冊的runascollection、injectioncollection、lifecyclecallbackcollection的邏輯實作。其中runascollection用于向注冊的對應的servletholder注冊runasrole資訊;injectioncollection實作從jndi中查找對應jndiname的執行個體,并将其設定到injection中指定的字段或方法中;lifecyclecallbackcollection用于實作在servlet、filter、eventlistener建立後或銷毀前調用相應的有@postconstruct注解或@predestroy注解的方法。

webappcontext繼承自servletcontexthandler,主要用于整合對servletcontexthandler的配置、配置webappclassloader、設定war包路徑、設定contextwhitelist、儲存metadata資訊等。

對webappcontext的配置,jetty使用configuration接口類抽象這個行為,其接口定義如下(方法名稱都比較直覺):

public interface configuration {

    public void preconfigure (webappcontext context) throws exception;

    public void configure (webappcontext context) throws exception;

    public void postconfigure (webappcontext context) throws exception;

    public void deconfigure (webappcontext context) throws exception;

    public void destroy (webappcontext context) throws exception;

    public void cloneconfigure (webappcontext template, webappcontext context) throws exception;

}

可以使用setconfigurationclasses或setconfigurations方法自定義目前支援的configuration集合,jetty預設添加集合有:webinfconfiguration、webxmlconfiguration、metainfconfiguration、fragmentconfiguration、jettywebxmlconfiguration,另外jetty内部預設實作的還有:annotationconfiguration、containerinitializerconfiguration、envconfiguration、plusconfiguration、tagligconfiguration等。

在webinfconfiguration實作中,在其preconfigure方法中,如果存在web-inf/work目錄,先在該目錄下建立一個名為jetty_<host>_<port>__<resourcebase>_<contextpath>_<virtualhost><base36_hashcode_of_whole_string>的臨時目錄,然後設定webappcontext的臨時目錄:

1. 可以手動設定。

2. 可以使用javax.servlet.context.tempdir屬性值設定。

3. 可以設定為${jetty.home}/work/jetty_<host>_<port>__<resourcebase>_<contextpath>_<virtualhost><base36_hashcode_of_whole_string>

4. 可以使用屬性org.eclipse.jetty.we 

pasting

 bapp.basetempdir指定的base,然後設定為${base}/jetty_<host>_<port>__<resourcebase>_<contextpath>_<virtualhost><base36_hashcode_of_whole_string>

5. 可以設定為${java.io.tmpdir}/jetty_<host>_<port>__<resourcebase>_<contextpath>_<virtualhost><base36_hashcode_of_whole_string>

6. 所有以上設定失敗,則使用file.createtempfile("jettycontext", "")的目錄來設定。

對于war包,如果配置了extractwar為true,則将war包解壓到war包所在目錄的war包名的目錄或<tempdir>/webapp目錄下,如果配置了copywebdir,則将原本配置的baseresource下的所有内容拷貝到<tempdir>/webapp目錄下,使用新的web_app目錄設定baseresource目錄;如果配置了copywebinf為true,則将web-inf/lib, web-inf/classes的兩個目錄拷貝到<tempdir>/webinf/lib, <tempdir>/webinf/classes目錄下,并更新baseresource為原來webapp目錄和<tempdir>/webinf兩個目錄的組合;設定org.eclipse.jetty.server.webapp.containerincludejarparttern屬性,查找urlclassloader中url中對應的jar包(即webappcontext中配置的extraclasspath值),并添加到metadata的containerjars集合中(如果不設定,則不會添加任何jar包);使用org.eclipse.jetty.server.webapp.webinfincludejarpattern屬性比對web-inf/lib目錄下的所有jar包,并添加到metadata的webinfjars集合中(如果不設定,預設添加所有jar包)。

在其configure()方法實作中,設定webappclassloader的classpath為web-inf/classes, web-inf/lib目錄下的所有jar包,并将這些jar包添加到webappclassloader中;并且如果配置了org.eclipse.jetty.resources屬性,則将配置的list<resource>集合添加到webappcontext的baseresource中。

webxmlconfiguration的實作,在preconfigure中,它向metadata注冊webdefault.xml檔案描述符;web.xml(預設為web-inf/web.xml)檔案描述符;以及override-web.xml檔案描述符;在注冊過程中解析它們的absolute-ordering資訊,将解析的結果合并到metadata的ordering集合中。在configure方法實作中,它向metadata添加standarddescriptorprocessor。

metainfconfiguration的實作,在preconfigure()方法中,掃描metadata中在webinfconfiguration中注冊的所有containerjars和webinfjars 的jar包,将找到的meta-inf/web-fragment.xml生成的resource注冊到org.eclipse.jetty.webfragments屬性中,在fragmentconfiguration中會被進一步添加到metadata中;将meta-inf/resources/對應的resource注冊到org.eclipse.jetty.resources屬性中,在webinfconfiguration的configure方法中會将這些目錄添加到baseresource集合中;将所有*.tld檔案對應的resource注冊到org.eclipse.jetty.tlds屬性中,在taglibconfiguration中,會對這些注冊tld檔案做進一步的處理。

fragmentconfiguration的實作,在其preconfigure方法中,将metainfconfiguration中找到的web-fragment.xml檔案對應的resource注冊到metadata中,在注冊中首先解析它的ordering資訊;在其configure方法中,它使用ordering中定義的順序邏輯對注冊的jar包進行排序。

在annotationconfiguration的實作中,在其configure()方法中,它首先向webappcontext中注冊annotationdecorator;然後它建立annotationparser執行個體,然後向其注冊webservletannotationhandler、webfilterannotationhandler、weblistenerannotationhandler、classinheritancehandler、containerinitializerannotationhandler,它們都實作了discoverableannotationhandler(其中classinheritancehandler實作的是classhandler接口);最後掃瞄所有classpath下jar包、web-inf/classes以及web-inf/lib中的jar包下的每個類,對于所有注冊為systemclasses,非serverclasses的類,使用classinheritancehandler紀錄所有類包含的直接子類以及所有接口包含的直接實作類,而webfilterannotationhandler、webservletannotationhandler、weblistenerannotationhandler用于注冊相應的webfilterannotation、webservletannotation、weblistenerannotation,并添加到metadata中discoveredannotation集合中,這些discoveredannotation在metadata的resolve方法(webappcontext.startcontext()方法中被調用)調用時會向webappcontext注冊對應的filterholder、servletholder、eventlistener,而containerinitializerannotationhandler則會将所有注冊的注解修飾的類添加到注冊的containerinitializer的annotatedtypenames集合中,該集合在containerinitializerconfiguration将它自身以及它的所有子類、實作類添加到applicabletypenames集合中,集合之前注冊的interestedtypes的所有子類、實作類傳遞到servletcontainerinitializer的onstartup()方法中。

在containerinitializerconfiguration會使用annotationconfiguration中注冊containerinitializer執行個體清單,建構applicabletypenames,并調用其servletcontainerinitializer的onstartup方法。

envconfiguration實作,在preconfigure方法中使用xmlconfiguration以及web-inf/jetty-env.xml檔案對webappcontext進行配置,并且綁定jndi環境。

taglibconfiguration實作,在preconfigure方法中向webappcontext注冊tagliblistener(servletcontextlistener),在tagliblistener的contextinitialized方法中,它首先查找所有能找到的web.xml中定義的*.tld檔案、web-inf/*.tld檔案、web-inf/tlds/*.tld檔案、以及通過webinfconfiguration在jar包中找到的*.tld檔案,将每個tld檔案解析成一個tlddescriptor,并且使用tldprocessor對它們進行解析成eventlistener清單,并注冊到webappcontext中。

plusconfiguration實作,在preconfigure中,它向webappcontext添加plusdecorator;在configure方法中添加plusdescriptorprocessor。 

在webappcontex啟動時:

1. 根據webappcontext的allowduplicatefragmentnames屬性設定metadata執行個體對應的屬性。

2. 調用preconfigure方法,它加載所有configuration執行個體(使用者自定義或預設設定:webinfconfiguration、webxmlconfiguration、metainfconfiguration、fragmentconfiguration、jettywebxmlconfiguration);加載系統類規則集合(即不能被web application覆寫的類,他們必須使用system classloader加載,可以通過server屬性org.eclipse.jetty.webapp.sysemclasses屬性定義,或者使用預設值)以及server類規則集合(不能被web application加載的類,他們需要使用system classloader加載,可以使用server屬性org.eclipse.jetty.webapp.serverclasses定義,或者使用預設值,這兩個的差別參考webappclassloader的實作解析);設定classloader,預設為webappclassloader;調用所有configuration的preconfigure方法。

3. 調用startcontext方法,他會調用configuration的configure方法,以及metadata的resolve方法;在metadata的resolve方法中,他首先設定webappcontext的javax.servlet.context.orderedlibs屬性,然後設定servletcontext的effectivemajorversion和effectiveminorversion,并周遊通過configuration注冊的descriptorprocessor,對webdefaultroots、webxmlroot、weboverrideroots等descriptor進行處理,以讀取servlet、filter、listener等資訊的定義,周遊在configuration中注冊的discoveredannotation,對所有找到的webfilter、webservlet、weblistener注解類進行解析并添加到weappcontext中,最後對在fragmentconfiguration中注冊的fragmentdescriptor以及discoveredannotation進行相應的處理已進一步配置webappcontext。

4. 調用postconfiguration方法,即調用所有注冊的configuration的postconfigure方法以做一些清理工作。

webappclassloader實作

webappclassloader是jetty中用于對servlet規範的classloader的實作,它內建子urlclassloader。它不會加載任何system class(使用system classloader加載),對java2中父classloader優先于子classloader的規則,可以使用webappcontext的setparentloadpriority為true來配置。如果沒有配置父classloader,則使用目前的thread context classloader,如果該classloader也為null,則使用加載該類的classloader,如果它還為null,則使用systemclassloader作為其父classloader。

在加載類時,webappclassloader有systemclass和serverclass的類别,systemclass是指在web application中可見的,但是他們不能被web application中的類(web-inf/classes,web-inf/lib中的類)覆寫的類,而serverclass是指這些類是server部分的實作,它對web application是不可見的,如果需要使用它們,可以将相應的jar包添加到web-inf/lib中。

webappclassloader預設支援.zip,.jar為擴充名的檔案中查找class定義,可以使用org.eclipse.jetty.webapp.webappclassloader.extensions系統屬性添加更多的擴充名檔案支援(以“,”或“;”分隔)。webappclassloader也會添加webappcontext中的extraclasspath到其classpath中(以“,”或“;”分隔),即添加url。

在webinfconfiguration的configure方法中,他會預設的将所有web-inf/lib下的jar包以及web-inf/classes目錄添加到webappclassloader的classpath中,即添加url。

在其loadclass的實作中,如果某class既是systemclass又是serverclass,則傳回null;如果不是serverclass,且是父classloader優先或者是systemclass,則使用父classloader加載,然後再使用目前classloader加載;在getresources和getresource的實作中,對于serverclass它隻能從目前classloader中查找,對systemclass它隻能從父classloader中查找。