天天看點

深入Jetty源碼之Servlet架構及實作(Servlet、Filter、Registration)

servlet是server applet的縮寫,即在伺服器端運作的小程式,而servlet架構則是對http伺服器(servlet container)和使用者小程式中間層的标準化和抽象。這一層抽象隔離了http伺服器的實作細節,而servlet規範定義了各個類的行為,進而保證了這些“伺服器端運作的小程式”對伺服器實作的無關性(即提升了其可移植性)。

在servlet規範有以下幾個核心類(接口):

servletcontext:定義了一些可以和servlet container互動的方法。

registration:實作filter和servlet的動态注冊。

servletrequest(httpservletrequest):對http請求消息的封裝。

servletresponse(httpservletresponse):對http響應消息的封裝。

requestdispatcher:将目前請求分發給另一個url,甚至servletcontext以實作進一步的處理。

servlet(httpservlet):所有“伺服器小程式”要實作了接口,這些“伺服器小程式”重寫doget、dopost、doput、dohead、dodelete、dooption、dotrace等方法(httpservlet)以實作響應請求的相關邏輯。

filter(filterchain):在進入servlet前以及出servlet以後添加一些使用者自定義的邏輯,以實作一些橫切面相關的功能,如使用者驗證、日志列印等功能。

asynccontext:實作異步請求處理。

在jetty中,每個servlet和其相關資訊都由servletholder封裝,并且将servlet相關操作代理給servletholder;同理,對filter也有filterholder與其相對應;另外servletholder和filterholder都繼承自holder執行個體。在servlet 3.0中引入動态向servletcontext注冊servlet和filter,并傳回相應的registration執行個體,用于進一步配置與其關聯的servlet和filter,因而registration也是和servletholde和filterholder相關聯的接口。在jetty中,他們的類關系圖如下:

深入Jetty源碼之Servlet架構及實作(Servlet、Filter、Registration)

servlet和filter是servlet規範中用于定義使用者邏輯實作的接口,servlet是最初的版本,所有的“伺服器端小程式”都要實作該接口,并交由servlet container管理其執行個體,負責其生命周期,以及當相應請求到來時調用相應方法。servlet接口非常簡單:

public interface servlet {

    // servlet container在建立一個servlet後調用該方法,并傳入servletconfig執行個體,進而使用者可以在這個方法中做一些自定義的初始化工作,如初始化資料庫連接配接等。

    // 并且servlet container可以保證一個servlet執行個體init方法之後被調用一次,但是對一個servlet類init方法可能會被多次調用,因而有些servlet container可能會在某些情況下将某些

    // servlet移出servlet container,而後又重新加載這些servlet,如為了在處理servlet container資源壓力比較大的情況下。

    public void init(servletconfig config) throws servletexception;

    // 傳回在init方法中傳入的servletconfig執行個體。servletconfig包含了在web.xml配置檔案中配置目前servlet的初始化參數,并且可以使用該servletconfig執行個體擷取目前servletcontext執行個體。

    public servletconfig getservletconfig();

    // 當該servlet對應的請求到來時,servlet container會調用這個servlet的service方法,用于處理請求,并将相應寫入servletresponse參數中,該方法隻能在init方法完成後被調用。

    // 在service方法中,可以選擇使用servletresponse定義的方法傳回響應給用戶端,或隻是向servletresponse中寫入響應,最終由servlet container根據servletresponse的資訊将響應傳回給用戶端。

    // 很多情況下,servlet container都不會使用多線程來處理客戶請求,應該該方法會在多線程環境下被使用,servlet實作者可以實作singlethreadmode接口以強制該方法隻在單線程的環境下被使用。

    // 但是singlethreadmode接口已經在servlet 2.3中被廢棄,實作該接口也會影響servlet的執行性能,而且有些servlet container會選擇執行個體或多個servlet執行個體,以保證對請求的響應性能,因而此時依然不能保證該方法的單線程特性,因而不建議使用這個singlethreadmode接口。

    public void service(servletrequest req, servletresponse res) throws servletexception, ioexception;

    // 傳回目前servlet的描述資訊,如作者、版本号、版權等。在genericservlet預設實作中,傳回空字元串。

    public string getservletinfo();

    // 當servlet container銷毀目前servlet時會調用該方法,進而給servlet提供擁有清理目前servlet占用的資源的地方,如關閉和資料庫的連接配接等。

    // servlet container會保證該方法在所有執行service方法的線程完成或逾時後被調用。servlet container一般會在三種情況下會調用該方法:

    // 1. servlet container目前資源比較緊張,需要将一些不再用或不常用的servlet移出;2. 實作某些servlet的熱部署;3. 目前web application被停止。

    public void destroy();

}

對每個servlet都有一個servletconfig執行個體和其對應,servletconfig中包含了在web.xml檔案中定義的servlet的init-param參數,并且可以使用servletconfig執行個體擷取servletcontext執行個體:

public interface servletconfig {

    // 傳回該servletconfig對應的servlet的名稱,在web.xml檔案中定義的servlet名稱或對于沒有注冊的servlet為其類名。

    public string getservletname();

    // 傳回和其關聯的servletcontext。

    public servletcontext getservletcontext();

    // 傳回在web.xml配置檔案中定義或者使用servletregistration動态定義添加的init-param參數。

    public string getinitparameter(string name);

    public enumeration<string> getinitparameternames();

在jetty中,不管是在web.xml中配置的servlet還是使用servletcontext動态注冊并使用servletregistration.dynamic動态配置的servlet,在servlethandler内部都使用servletholder來表示一個servlet,并且由servletholder來處理所有和servlet相關的邏輯。servletholder的實作邏輯在之後給出。

servlet架構中預設實作了兩個servlet:genericservlet和httpservlet,genericservlet隻是對servlet的簡單實作,而httpservlet會根據請求中的方法将請求分發給相應的:doget/dopost/doput/dohead/dooptions/dotrace等方法,它還提供了getlastmodified()方法,重寫該方法用于實作條件get。以上這些方法(除dooptions和dotrace已經有具體的邏輯實作)預設實作直接方法405 method not allowed響應(http/1.1)或404 bad request響應(http/1.0)。重寫相應的方法以實作各個method對應的邏輯。

在servlet 2.3開始引入了filter機制,以在servlet的service方法的執行前後添加一些公共的filter邏輯,為面向切面的程式設計提供了很大的便利,這些公共的邏輯如紀錄一個request從進入servlet container到出所花費的總時間、為每個request添加一些額外的資訊以幫助之後處理、對所有或特定request添加使用者驗證功能等。filter可以在web.xml檔案中定義,由servlet container負責其執行個體化、初始化以及dofilter方法的調用。在servlet 3.0以後,還支援動态的給servletcontext注冊filter,并由傳回的filterregistration.dynamic執行個體做進一步的配置。filter的接口定義也是比較簡單:

public interface filter {

    // 由servletcontainer在初始化一個filter時調用,filter的實作者可以在該方法中添加一些使用者自定義的初始化邏輯,同時可以儲存filterconfig執行個體,它可以擷取定義的init-param初始化參數以及擷取servletcontext執行個體。其他情況和servlet類似,不贅述。

    public void init(filterconfig filterconfig) throws servletexception;

    // 每一次請求到來都會穿越配置的filterchain,執行配置的servlet,然後從這個filterchain中傳回。在dofilter方法的實作中,要調用下一個filter,使用filterchain的dofilter方法。

    // 在調用filterchain的dofilter之前為執行請求之前的處理,而之後為請求已經執行完成,在響應傳回的路上的邏輯處理。也可以步調用filterchain的dofilter方法,以阻止請求的進一步處理。

    public void dofilter(servletrequest request, servletresponse response, filterchain chain)

            throws ioexception, servletexception;

    // 在filter被移出servlet container時調用,它和servlet類似,不贅述。**

filter接口中包含對filterconfig以及filterchain的使用,filterconfig和servletconfig定義、實作以及邏輯都類似:

public interface filterconfig {

    public string getfiltername();

filterchain是servlet中将filter鍊以channel的方式連接配接在一起的類,它實作了可以向執行servlet前和後都添加一些切面邏輯,甚至阻止servlet的執行,filter的實作者使用filterchain的dofilter方法調用在這個鍊中的下一個filter的dofilter方法,如果目前filter是鍊中最後一個filter,則調用響應的servlet。其接口定義如下:

public interface filterchain {

    // 調用該方法會調用filter鍊中的下一個filter的dofilter方法,如果目前filter是這條鍊中的最後一個filter,則該方法會調用響應的servlet的service方法。

    public void dofilter (servletrequest request, servletresponse response) throws ioexception, servletexception;

在jetty中,servlethandler的内部類chain實作了filterchain接口,在構造chain執行個體時,首先根據request的url以及對應servlet name查找所有相關的filter清單,然後使用這個filter清單、request執行個體、目前請求對應的servletholder建立這個鍊,在其dofilter方法實作中,它會存儲一個_filter索引,它指向下一個filter執行個體,當每個filter調用dofilter方法時,chain會根據這個索引擷取下一個filter執行個體,并将該索引向後移動,進而調用下一個filter的dofilter方法,如果這個索引值到達最後一個filter鍊中的filter,且有servletholder執行個體存在,則調用該servletholder的handle方法,否則調用notfound方法,即向用戶端發送404 not found響應。如果filter不支援async模式,則在調用其dofilter之前,需要将request的async支援設定為false。

在servlethandler中還有cachedchain實作了filterchain接口,它以連結清單的形式紀錄找到的filter清單,并将這個清單緩存在servlethandler中,不同的dispatch類型有一個清單,并且可以根據請求的url或請求的servlet名來查找是否已經有緩存的filter連結清單。

在servlethandler中,可以使用setfilterchainscached方法來配置是否使用cachedchain還是直接使用chain,預設使用cachedchain。

registration是servlet 3.0規範中引入的接口,用于表示向servletcontext中動态注冊的servlet、filter的執行個體,進而實作對這些動态注冊的servlet、filter執行個體進行進一步的配置。 對于servlet和filter的配置,他們的共同點是他們有響應的name、classname、init parameters以及asyncsupported屬性,而這些方法正是registration接口的定義。registration接口将setasyncsupport方法定義在其内部的dynamic接口中,dynamic用于表示這是用于動态的配置這個servlet或filter的含義,但是為什麼要将這個方法放在dynamic接口中呢?如何決定不同的方法應該是在registration本身的接口中,而那些應該放到dynamic接口中呢?

public interface registration {

    // 傳回這個registration執行個體關聯的servlet或filter的name,這個name在向servletcontext注冊servlet或filter時給定。

    public string getname();

    // 傳回這個registration執行個體關聯的servlet或filter的類名。

    public string getclassname();

    // 和這個registration執行個體關聯的初始化參數的操作。

    public boolean setinitparameter(string name, string value);

    public set<string> setinitparameters(map<string, string> initparameters);

    public map<string, string> getinitparameters();

    interface dynamic extends registration {

        // 配置registration關聯的servlet或filter是否支援異步操作。

        public void setasyncsupported(boolean isasyncsupported);

    }

registration有兩個子接口:servletregistration和filterregistration,分别用于表示servlet相關的配置和filter相關的配置。

對servletregistration,它可以設定servlet的url mapping、runasrole屬性、loadonstartup屬性等:

public interface servletregistration extends registration {

    // 添加url patterns到這個servletregistration關聯的servlet的映射。如果有任意的url patterns已經映射到其他的servlet中,則該方法不會執行任何行為。

    public set<string> addmapping(string... urlpatterns);

    // 擷取所有到目前servletregistration對應的servlet的url patterns。

    public collection<string> getmappings();

    // 擷取目前servletregistration對應的servlet的runasrole。

    public string getrunasrole();

    interface dynamic extends servletregistration, registration.dynamic {

        // 設定目前servletregistration對應的servlet的loadonstartup等級。如果loadonstartup大于或等于0,表示servlet container要優先初始化該servlet,

        // 此時servlet container要在container初始化時執行個體化并初始化該servlet,即在所有注冊的contextlistener的contextinitialized方法調用完成後。

        // 如果loadonstartup小于0,則表示這個servlet可以在用到的時候執行個體化并初始化。預設值為-1。

        public void setloadonstartup(int loadonstartup);

        // 設定servletregistration相關的servletsecurityelement屬性。

        public set<string> setservletsecurity(servletsecurityelement constraint);

        // 設定servletregistration對應的servlet的multipartconfigelement屬性。

        public void setmultipartconfig(multipartconfigelement multipartconfig);

        // 設定servletregistration對應的servlet的runasrole屬性。

        public void setrunasrole(string rolename);

對filterregistration,它可以配置filter的url mapping和servlet mapping等:

public interface filterregistration extends registration {

    // 添加filterregistration關聯的filter到servlet的映射,使用servlet name、dispatchertype作為映射。ismatchafter表示新添加的mapping是在已有的mapping之前還是之後。

    public void addmappingforservletnames(enumset<dispatchertype> dispatchertypes, boolean ismatchafter, string... servletnames);

    // 擷取目前filterregistration關聯的filter已存在的到servlet name的映射。

    public collection<string> getservletnamemappings();

    // 添加filterregistration關聯的filter到servlet的映射,使用url patterns、dispatchertype作為映射。ismatchafter表示新添加的mapping是在已有的mapping之前還是之後。

    public void addmappingforurlpatterns(enumset<dispatchertype> dispatchertypes, boolean ismatchafter, string... urlpatterns);

    // 擷取目前filterregistration關聯的filter已存在的到url patterns的映射。

    public collection<string> geturlpatternmappings();

    interface dynamic extends filterregistration, registration.dynamic {

在jetty中對registration的實作在holder中定義,而相應的servletregistration和filterregistration實作作為servletholder和filterholder中的内部類實作,具體參考這兩個類的實作。

在之前有提到,在jetty中servlet和filter由相應的servletholder和filterholder封裝,以将servlet/filter相關的資訊和配置放在一起,并處理各自相關的邏輯,即面向對象設計中的将資料靠近操作。由于servlet和filter有一些相同的配置和邏輯,因而在servletholder和filterholder中提取出了holder父類。在holder的實作中,它主要定義了一些servlet和filter都要使用的字段,比高實作了所有和initparameter相關的操作:

    public enum source { embedded, javax_api, descriptor, annotation };

    final private source _source;

    protected transient class<? extends t> _class;

    protected final map<string,string> _initparams=new hashmap<string,string>(3);

    protected string _classname;

    protected string _displayname;

    protected boolean _extinstance;

    protected boolean _asyncsupported=true;

    protected string _name;

    protected servlethandler _servlethandler;

holder繼承自abstractlifecycle,它在start時,如果_class字段沒有被設定,則會使用classloader加載_classname中指定的類執行個體并指派給_class字段;source隻是目前隻是一種中繼資料的形式存在,用于表示servlet或filter的來源;而extinstance用于表示servlet和filter執行個體是直接通過servletcontext注冊而來,而不是在目前holder内部建立。

在holder類中還定了兩個内部類:holderconfig和holderregistration,其中holdconfig實作了servletconfig/filterconfig相關的所有initparameter相關的操作(代理給holder);holderregistration實作了registration.dynamic接口,其實作也都代理給holder類中的方法。

filterholder實作比較簡單,它直接繼承自holder類,它額外的包含了一下幾個字段:

    private transient filter _filter;

    private transient config _config;

    private transient filterregistration.dynamic _registration;

其中_filter字段在start時如果沒有初始化,則使用servletcontext建立該filter執行個體,而_config字段則在啟動時直接建立config執行個體(config是filterholder的内部類,且它繼承自holderconfig,并實作了filterconfig接口),最後調用_filter.init()方法并傳入_config執行個體。在stop時,調用_filter.destroy()方法進而該filter有機會做一些清理工作,并且調用servlethandler中的destroyfilter()方法,以通知contexthandler中定義的decorators。在注冊外部執行個體化的filter時,設定_extinstance為true,同時更新_class字段,以及_name字段(如果_name字段未被設定的話)。

最後,filterholder中還定義了registration内部類,它繼承自holderregistration,并實作了filterregistration.dynamic接口。該registration内部類實作了mapping相關方法,jetty中使用filtermapping來表達一個filter的映射關系。在filtermapping中定義了一下映射關系:

    private int _dispatches=default;

    private string _filtername;

    private transient filterholder _holder;

    private string[] _pathspecs;

    private string[] _servletnames;

它包含了兩個appliesto()方法,這兩個方法在chain用于根據dispatchertype或dispatchertype以及path計算目前filtermapping是否比對給定的dispatchertype或dispatchertype和path。在對dispatchertype做比對計算時,使用filtermapping執行個體的沒有設定dispatchertype集合,它依然比對request或async(如果filter支援async模式的話)。

    boolean appliesto(int type) {

        if (_dispatches==0)

            return type==request || type==async && _holder.isasyncsupported();

        return (_dispatches&type)!=0;

servletholder實作相對複雜,它繼承自holder類,并實作了useridentity.scope接口以及comparable接口,其中comparable接口用于當servlethandler在start時,對注冊的所有servletholder的數組排序以決定他們的start順序。它包含了一下額外的字段:

    private int _initorder;

    private boolean _initonstartup=false;

    private map<string, string> _rolemap;

    private string _forcedpath;

    private string _runasrole;

    private runastoken _runastoken;

    private identityservice _identityservice;

    private servletregistration.dynamic _registration;

    private transient servlet _servlet;

    private transient long _unavailable;

    private transient unavailableexception _unavailableex;

    public static final map<string,string> no_mapped_roles = collections.emptymap();

其中_initorder為servletregistration.dynamic接口的實作,它定義servletholder在servlethandler中的start順序,即compareto()方法的實作的主要參考資訊。在jetty中,隻要設定了loadonstart字段,則它就會在start時被初始化(即使設定的值為負數)。在設定外部servlet執行個體直接到servletholder中時,_extinstance字段為被設定為true。如果在web.xml中的servlet定義中有jsp-file定義,則設定該servletholder的forcepath值為該jsp-file中定義的值,而其classname的值為servlet-class的定義,此時在servletholder的handle方法中會将forcepath的值設定到request的org.apache.catalina.jsp_file屬性中(這個設定用來幹嘛呢?還不了解。。。);在servletholder啟動時,如果servlet執行個體實作了singlethreadmodel接口,則servlet執行個體使用singlethreadedwrapper類來表示(它包含一個servlet棧 ,對每次請求,它會複用以前已經建立的servlet執行個體或者建立一個新的執行個體以處理目前的請求,即該servlet會有多個執行個體),如果_extinstance為true或者配置了loadonstart屬性,則在servletholder啟動是就會初始化這個servlet(執行個體化servlet,并調用servlet的init方法)。當一個請求到來時,servletholder調用其handle方法以處理該請求,在該方法中調用servlet的service方法,如果在處理過程中出錯了,則在request中設定javax.servlet.error.servlet_name屬性為servletholder的name屬性。

類似filtermapping,jetty也使用servletmapping表達servlet和url pattern的映射關系:

    private string[] _pathspecs;

    private string _servletname;

其他security相關的字段和邏輯暫不做介紹。。。。。