三、容器Container
Container 是容器的父介面,所有子容器都必須實現這個介面。
Container 容器的設計用的是典型的責任鏈的設計模式,它有四個子容器元件構成,分別是:Engine、Host、Context、Wrapper,這四個元件不是平行的,而是父子關係,Engine 包含 Host,Host 包含 Context,Context 包含 Wrapper。通常一個 Servlet class 對應一個 Wrapper,如果有多個 Servlet 就可以定義多個 Wrapper。
容器定義 | 標準實現類 | 基礎閥類 | 其他 |
Engine: 表示整個Catalina servlet引擎 |
StandardEngine | StandardEngineValue |
使用對映器Mapper返回子容器中特定的Host |
Host: 表示包含一個或者多個Context容器的虛擬主機 |
StandardHost | StandardHostValue | 使用對映器Mapper返回子容器中特定的context |
Context: 表示一個Web應用程式 |
StandardContext | StandardContextValue | 使用對映器Mapper返回子容器中特定的Wrapper |
Wrapper: 表示一個獨立的servlet |
StandardWrapper | StandardWrapperValue |
呼叫其invoke方法之前先要執行過濾器鏈 會呼叫具體servlet的service(),該service預設是無狀態可併發的 |
四、容器Container如何處理請求
我們現在解決傳遞到container的請求是如何找到對應的servlet的,首先關注connector啟動的時候Container的初始化流程:
public static void main(String[] args) { HttpConnector connector = new HttpConnector(); //兩個wapper Wrapper wrapper1 = new SimpleWrapper(); wrapper1.setName("Primitive"); wrapper1.setServletClass("PrimitiveServlet"); Wrapper wrapper2 = new SimpleWrapper(); wrapper2.setName("Modern"); wrapper2.setServletClass("ModernServlet"); //建立context和wapper的關係 Context context = new SimpleContext(); context.addChild(wrapper1); context.addChild(wrapper2); //建立兩個閥,並放到context的管道中 Valve valve1 = new HeaderLoggerValve(); Valve valve2 = new ClientIPLoggerValve(); ((Pipeline) context).addValve(valve1); ((Pipeline) context).addValve(valve2); // 建立Mapper,用於對映wrapper // context.addServletMapping(pattern, name); Mapper mapper = new SimpleContextMapper(); mapper.setProtocol("http"); context.addMapper(mapper);
context.addServletMapping("/Primitive", "Primitive"); context.addServletMapping("/Modern", "Modern");
//建立class loader
Loader loader = new SimpleLoader();
context.setLoader(loader); //建立聯結器和容器的關係
connector.setContainer(context);
try {
connector.initialize(); //啟動聯結器
connector.start();
}
catch (Exception e) { e.printStackTrace(); }
}
}
1、管道Pipeline:我們這裡看到了Pipeline,可以理解為Context容器內部維護了一個管道,可以向這個管道中新增各式各樣的閥。
當invoke呼叫到達context容器後,下面實際呼叫的是Pipeline的invoke方法,該方法依次遍歷管道上的各種閥,一個閥處理完成後,交個下一個閥進行處理,該設計模式為責任鏈模式。基礎閥總是隨後一個執行,基礎閥一般用來找到對應的子容器warpper並呼叫子容器的service()方法。
2、對映器Mapper:那麼是如何找到對應的servlet的呢,這裡用到了對映器Mapper,context根據對映規則就可以找到對應的servlet,執行對應的service(),至此,一個處理請求完畢。
3、總體過一遍:聯結器收到請求後,建立request/response,呼叫StandardContext的invoke方法;該invoke方法呼叫其管道物件的invoke方法,管道物件的invoke方法會依次呼叫其閥的invoke,最後呼叫基礎閥的invoke;基礎閥是StandardContextValue類的例項,基礎閥的invoke方法被呼叫,基礎閥invoke會獲取Wrapper例項,並依次執行Warpper的管道物件的閥,最終通過Mapper找到對應的servlet,執行servlet的service()方法。
五、容器Container生命週期管理Lifecycle
catalina允許一個元件包含多個其他元件,當父元件啟動和關閉的時候,子元件會跟著一起啟動和關閉,該部分關鍵是Lifecycle介面
public interface Lifecycle { public void addLifecycleListener(LifecycleListener listener); public void removeLifecycleListener(LifecycleListener listener); public void start() throws LifecycleException; public void stop() throws LifecycleException; }
1、啟動/關閉的時候,會依次遍歷註冊到該容器的子容器,呼叫子容器的start/stop方法,像鏈式爆炸一樣,把整個關聯的容器都啟動/關閉
2、事件Listener會預先呼叫容器的addLifecycleListener方法,將事件監聽器加到該容器維護的監聽器陣列中,於是容器在特定的時機呼叫 lifecycle.fireLifecycleEvent(START_EVENT, null);就會遍歷這些監聽器,呼叫這些監聽器的lifecycleEvent()方法,這是一種典型的釋出者訂閱者模式的應用。
六、載入器Loader
tomcat使用自定義載入器的原因:
1、servlet應該只允許載入web-inf/classes、web-inf/lib下的類,訪問其餘目錄(環境變數中classpath指定的路徑)會是危險的,因此要設定一些載入規則。
2、將載入的類快取起來
3、支援自動過載的功能。當web-inf/classes、web-inf/lib下的類發生變化的時候,web應用會重新載入這些類。tomcat實現該功能的方法是使用一個額外的執行緒不斷地檢查這些類的時間戳,發現變動後會自動載入。
支援自動過載功能,需要設定server.xml
<Context docBase="D:\ProgramServer\Tomcat7\stswebapps\traveller" path="/traveller" reloadable="true" source="org.eclipse.jst.jee.server:traveller"/>
org.apache.catalina.loader.webappLoader類實現Loader介面,作為應用載入器,負責載入web應用程式中使用到的類。
WebappLoader類會建立org.apache.catalina.loader.WebappClassLoader類的例項作為類載入器,WebappClassLoader預設會使用java.net.URL.ClassLoader具體承擔載入類的工作。
WebappClassLoader會快取它所載入的類,它會維護一個Vector物件,儲存已經載入的類,防止這些類在不適用的時候被當做垃圾回收。
七、Session管理器
1、session物件怎麼儲存的
Context容器的Session管理器會管理Context容器中所有活動的Session物件,這些活動的Session物件都儲存在一個名為sessions的HashMap變數中
2、session物件超時失效是怎麼做到的
Session管理器通過建立一個專門的執行緒,來實現銷燬已經失效的Session物件的。其思想就是週期地遍歷Session管理器管理的所有Session物件,將Session物件的lastAccessedTime和當前時間進行比較,如果兩者的差值超過了某個常數,就會呼叫Session物件的expire()方法使其失效。
3、session物件的持久化
session管理器可以將session物件持久化,儲存到檔案儲存器或者通過JDBC儲存到資料庫中。以便再次啟動的時候重新讀入記憶體,維持session的狀態不變化。
在持久化session管理器中,session可以備份,也可以換出。當備份一個session物件的時候,該Session物件會被複制到儲存器中,而原物件依然保留在記憶體中。當物件被換出,意思是當前活動的session物件超過了上限值,或者這個session物件閒置了過長的時間,之所以換出是為了節省時間。
因為session物件可以被換出,所以它既可能儲存在記憶體中,也可能儲存在儲存器中,因此,當客戶端需要訪問session資料的時候,findSession()方法會在記憶體中查詢是否存在該session例項,如果沒有找到,就到儲存器中去查詢。
public Session findSession(String id) throws IOException { Session session = super.findSession(id); if (session != null) return (session); // See if the Session is in the Store session = swapIn(id); return (session); }