Servlet工作原理解析2(以Tomcat為例)


      Servlet如何工作

      Servlet是如何被加載的、如何被初始化的,以及Servlet的體系結構都已經清楚了,現在就看看它是如何被調用的。

      用戶從瀏覽器向服務器發起的一個請求通常會包含如下信息:http://hostname: port/contextpath/serletpath,hostname和port用來與服務器建立TCP連接,后面的URL才用來選擇在服務器中那個子容器服務用戶的請求。服務器是如何根據這個URL來到達正確的Servlet容器中的呢?在Tomcat7中這件事很容易解決,因為這種映射工作由專門一個類來完成,這個類就是org.apache.tomcat.util.http.mapper,這個類保存了Tomcat的Container容器中的所有子容器的信息,org.apache.catalina.connector.Request類在進入Container容器前,Mapper將會根據這次請求的hostname和contextpath將host和context容器設置到Request的mappingData屬性中,如下圖所示。所有當Request進入Container容器前,要訪問哪個子容器就已經確定了。

 

      但是在Mapper中怎么會有容器的完整關系呢?這要看MapperListener類的初始化過程了,如下代碼:

      public void init(){

           findDefaultHost();

           Engine  engine  =  (Engine)connector.getService().getContainer();

           engine.addContainerListener(this);

           Container[]  conHosts  =  engine.findChildren();

           for(Container  conHost  :  conHosts){

                 Host  host  =  (Host)conHost;

                 if(!LifecycleState.NEW.equals(host.getState())){

                      host.addLifecycleListener(this);

                      registerHost(host);

                 }

           }          

      }

      這段代碼是將MapperListener類作為一個監聽者加到整個容器的每個子容器中,這樣只要任何一個容器發生變化,MapperListener都將會被通知到,相應的保存容器關系的MapperListener的mapper屬性也會被修改。在for循環中就是將host及下面的子容器注冊到mapper中。

      下圖是Request在容器中的路由圖:

 

      請求是如上圖到達正確的Wrapper容器的,但是在請求到達最終的Servlet前還要完成一些步驟,必須要執行Filter鏈,以及通知你在web.xml中定義的listener。接下來要執行Servlet的service方法了。通常我們自己定義的servlet並不直接去實現javax.servlet.servlet接口,而是去繼承更簡單的HttpServlet類或者GenericServlet類,這樣可以有選擇地覆蓋相應的方法去實現要完成的工作。

      Servlet的確已經能夠幫我們完成所有的工作了,但是現在的Web應用很少直接將交互的全部頁面用Servlet來實現,而是采用更加高效的MVC框架來實現。它們的基本原理是將所有的請求都映射到一個Servlet,然后去實現service方法,這個方法也就是MVC框架的入口。

      當Servlet從Servlet容器中移除時,也就表明該Servlet的生命周期結束了,這時Servlet的destroy方法將被調用,做一些掃尾工作。

 

 

      Servlet中的Listener

      在整個Tomcat服務器中,Listener使用得非常廣泛,它是基於觀察者模式設計的,能夠從另一個縱向維度控制程序和數據,是一種快捷的手段。

      如下圖,Servlet中的Listener:

 

      實際上這6個Listener都繼承了EventListener接口,每個Listener各自定義了需要實現的接口。這些個接口基本上涵蓋了整個Servlet生命周期的各種事件。這些Listener的實現類可以配置在web.xml的 <listener>標簽中,也可以在應用程序中動態添加Listener,需要注意的是ServletContextListener在容器啟動后就不能再添加新的,因為它監聽的事件不會再出現了。掌握這些Listener的使用方法,能夠讓我們的程序設計的更加靈活。如spring的org.springframework.web.context.ContextLoaderListener就實現了一個ServletContextListener,當容器加載時啟動spring容器。ContextLoaderListener在contextInitialized方法中初始化spring容器,有幾種辦法可以加載spring容器,通過在web.xml的<context-param>標簽中配置spring的applicationContext.xml路徑,文件名可以任意取,如果沒有配置,將在/WEB/INF/路徑下查找默認的applicationContext.xml文件。ContextLoaderListener的contextInitialized方法代碼如下:

      public  void  contextInitialized(ServletContextEvent  event){

           this.contextLoader  =  createContextLoader();

           if(this.contextLoader  ==  null){

                  this.contextLoader  =  this;

           }

           this.contextLoader.initWebApplicationContext(event.getServlet-Context());

      }

 

 

 

      Filter如何工作

      Filter也是在web.xml中另外一個常用的配置項,可以通過<filter>和<filter-mapping>組合來使用它。實際上Filter可以完成與Servlet同樣的工作,甚至比Servlet使用起來更加靈活,因為它除了提供了request和response外,還提供了一個FilterChain對象,這個對象讓我們更加靈活地控制請求的流轉。

      在Tomcat中,FilterConfig和FilterChain的實現類分別是ApplicationFilterConfig和ApplicationFilterChain,而Filter的實現類有用戶自定義,只要實現Filter接口中定義的三個接口就行,這三個接口在與Servlet中的類似。只不過還有一個ApplicationFilterChain類,它可以將多個Filter串聯起來,組成一個鏈,這個鏈與Jetty中的Handler鏈有異曲同工之妙。下面是Filter類中的三個接口方法:

      1.init(FilterConfig):初始化接口,在用戶自定義的Filter初始化時被調用,它與Servlet的init方法的作用是一樣的,FilterConfig與ServletConfig也類似,除了都能渠道容器的環境類ServletContext對象外,還能獲取在<filter>下配置的<init-param>參數值。

      2.doFilter(ServletRequest, ServletResponse, FilterChain):在每個用戶請求進來時這個方法都會被調用,並在Servlet的service方法前被調用。而FilterChain就代表當前的整個請求鏈,所以可以通過FilterChain.doFilter()將請求繼續傳遞下去。如果想攔截這個請求,就不調用FilterChain.doFilter(),那么這個請求就不返回了。所以Filter是一種責任鏈模式。

      3.destroy:當Filter對象被銷毀時,這個方法被調用。注意,當Web容器調用這個方法之后,容器會再調用一次doFilter方法。

 

      Filter類的核心還是傳遞的FilterChain對象,這個對象保存了到最終Servlet對象的所有Filter對象,這些對象都保存在ApplicationFilterChain對象的filters數組中。在FilterChain鏈上每執行一個Filter對象,數組的當前計數都會加1,知道計數等於數組的長度,當FilterChain上所有的Filter對象執行完成后,就會執行最終的Servlet。所以在ApplicationFilterChain對象中會持有Servlet對象的引用。下圖是Filter對象的執行時序圖:

      Servlet中url-pattern

      在web.xml中<servlet-mapping>和<filter-mapping>都有<url-pattern>配置項,它們的作用都是匹配一次請求是否會執行這個Servlet或者Filter,那么這個URL是怎么匹配的呢?又是何時匹配的呢?

      一個請求最終被分配到一個servlet中是通過org.apache.tomcat.util.http.Mapper類完成的,這個類會 根據請求的URL來匹配在每個Servlet中配置的<url-pattern>,所以它在一個請求被創建時就已經匹配了。Filter的url-pattern匹配是在創建ApplicationFilterChain對象時進行的,它會把所有定義的Filter的url-pattern與當前的url匹配如果匹配成功就將這個Filter保存到ApplicationFilterChain的filter數組中,然后在FilterChain中依次調用。

      在web.xml加載時,首先會檢查<url-pattern>配置是否符合規則,這個檢查是在StandardContext的validateURLPattern方法中進行的,如果檢查不成功,Context容器啟動會失敗,並且會報java.lang.IllegalArgumentException:Invalid<url-pattern>/a/*.htm in Servlet mapping錯誤。

      <url-pattern>的解析規則,對Servlet和Filter是一樣的,匹配規則有如下三種:

      1.精確匹配:如/foo.htm只會匹配foo.htm這個URL

      2.路徑匹配:如/foo/*會匹配以foo為前綴的URL

      3.后綴匹配:如*.htm會匹配所有以.htm為后綴的URL

      Servlet的匹配規則在org.apache.tomcat.util.http.mapper.Mapper.internalMapWrapper中定義,對Servlet的匹配來說如果同時定義了多個<url-pattern>,那么到底匹配哪個Servlet呢?這個匹配順序是:首先精確匹配,然后是路徑匹配,最后是后綴匹配。

      Filter的匹配規則在ApplicationFilterFactory.matchFiltersURL方法中定義。Filter的匹配原則和Servlet有些不同,只有匹配成功,這些Filter都會在請求鏈上被調用。

      

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM