servlet工做原理

從 Servlet 容器提及

要介紹 Servlet 必需要先把 Servlet 容器說清楚,Servlet 與 Servlet 容器的關係有點像槍和子彈的關係,槍是爲子彈而生,而子彈又讓槍有了殺傷力。雖然它們是彼此依存的,可是又相互獨立發展,這一切都是爲了適應工業化生產的結果。從技術角度來講是爲了解耦,經過標準化接口來相互協做。既然接口是鏈接 Servlet 與 Servlet 容器的關鍵,那咱們就從它們的接口提及。java

前面說了 Servlet 容器做爲一個獨立發展的標準化產品,目前它的種類不少,可是它們都有本身的市場定位,很難說誰優誰劣,各有特色。例如如今比較流行的 Jetty,在定製化和移動領域有不錯的發展,咱們這裏仍是以你們最爲熟悉 Tomcat 爲例來介紹 Servlet 容器如何管理 Servlet。Tomcat 自己也很複雜,咱們只從 Servlet 與 Servlet 容器的接口部分開始介紹,關於 Tomcat 的詳細介紹能夠參考個人另一篇文章《 Tomcat 系統架構與模式設計分析》。web

Tomcat 的容器等級中,Context 容器是直接管理 Servlet 在容器中的包裝類 Wrapper,因此 Context 容器如何運行將直接影響 Servlet 的工做方式。apache

圖 1 . Tomcat 容器模型

圖 1 . Tomcat 容器模型

從上圖能夠看出 Tomcat 的容器分爲四個等級,真正管理 Servlet 的容器是 Context 容器,一個 Context 對應一個 Web 工程,在 Tomcat 的配置文件中能夠很容易發現這一點,以下:後端

清單 1 Context 配置參數
 <Context path="/projectOne " docBase="D:\projects\projectOne" 
 reloadable="true" />

下面詳細介紹一下 Tomcat 解析 Context 容器的過程,包括如何構建 Servlet 的過程。設計模式

Servlet 容器的啓動過程

Tomcat7 也開始支持嵌入式功能,增長了一個啓動類 org.apache.catalina.startup.Tomcat。建立一個實例對象並調用 start 方法就能夠很容易啓動 Tomcat,咱們還能夠經過這個對象來增長和修改 Tomcat 的配置參數,如能夠動態增長 Context、Servlet 等。下面咱們就利用這個 Tomcat 類來管理新增的一個 Context 容器,咱們就選擇 Tomcat7 自帶的 examples Web 工程,並看看它是如何加到這個 Context 容器中的。瀏覽器

清單 2 . 給 Tomcat 增長一個 Web 工程
 Tomcat tomcat = getTomcatInstance(); 
 File appDir = new File(getBuildDirectory(), "webapps/examples"); 
 tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath()); 
 tomcat.start(); 
 ByteChunk res = getUrl("http://localhost:" + getPort() + 
               "/examples/servlets/servlet/HelloWorldExample"); 
 assertTrue(res.toString().indexOf("<h1>Hello World!</h1>") > 0);

清單 1 的代碼是建立一個 Tomcat 實例並新增一個 Web 應用,而後啓動 Tomcat 並調用其中的一個 HelloWorldExample Servlet,看有沒有正確返回預期的數據。tomcat

Tomcat 的 addWebapp 方法的代碼以下:服務器

清單 3 .Tomcat.addWebapp
 public Context addWebapp(Host host, String url, String path) { 
        silence(url); 
        Context ctx = new StandardContext(); 
        ctx.setPath( url ); 
        ctx.setDocBase(path); 
        if (defaultRealm == null) { 
            initSimpleAuth(); 
        } 
        ctx.setRealm(defaultRealm); 
        ctx.addLifecycleListener(new DefaultWebXmlListener()); 
        ContextConfig ctxCfg = new ContextConfig(); 
        ctx.addLifecycleListener(ctxCfg); 
        ctxCfg.setDefaultWebXml("org/apache/catalin/startup/NO_DEFAULT_XML"); 
        if (host == null) { 
            getHost().addChild(ctx); 
        } else { 
            host.addChild(ctx); 
        } 
        return ctx; 
 }

前面已經介紹了一個 Web 應用對應一個 Context 容器,也就是 Servlet 運行時的 Servlet 容器,添加一個 Web 應用時將會建立一個 StandardContext 容器,而且給這個 Context 容器設置必要的參數,url 和 path 分別表明這個應用在 Tomcat 中的訪問路徑和這個應用實際的物理路徑,這個兩個參數與清單 1 中的兩個參數是一致的。其中最重要的一個配置是 ContextConfig,這個類將會負責整個 Web 應用配置的解析工做,後面將會詳細介紹。最後將這個 Context 容器加到父容器 Host 中。cookie

接下去將會調用 Tomcat 的 start 方法啓動 Tomcat,若是你清楚 Tomcat 的系統架構,你會容易理解 Tomcat 的啓動邏輯,Tomcat 的啓動邏輯是基於觀察者模式設計的,全部的容器都會繼承 Lifecycle 接口,它管理者容器的整個生命週期,全部容器的的修改和狀態的改變都會由它去通知已經註冊的觀察者(Listener),關於這個設計模式能夠參考《 Tomcat 的系統架構與設計模式,第二部分:設計模式》。Tomcat 啓動的時序圖能夠用圖 2 表示。網絡

圖 2. Tomcat 主要類的啓動時序圖(查看大圖

圖 2. Tomcat 主要類的啓動時序圖

上圖描述了 Tomcat 啓動過程當中,主要類之間的時序關係,下面咱們將會重點關注添加 examples 應用所對應的 StandardContext 容器的啓動過程。

當 Context 容器初始化狀態設爲 init 時,添加在 Contex 容器的 Listener 將會被調用。ContextConfig 繼承了 LifecycleListener 接口,它是在調用清單 3 時被加入到 StandardContext 容器中。ContextConfig 類會負責整個 Web 應用的配置文件的解析工做。

ContextConfig 的 init 方法將會主要完成如下工做:

  1. 建立用於解析 xml 配置文件的 contextDigester 對象
  2. 讀取默認 context.xml 配置文件,若是存在解析它
  3. 讀取默認 Host 配置文件,若是存在解析它
  4. 讀取默認 Context 自身的配置文件,若是存在解析它
  5. 設置 Context 的 DocBase

ContextConfig 的 init 方法完成後,Context 容器的會執行 startInternal 方法,這個方法啓動邏輯比較複雜,主要包括以下幾個部分:

  1. 建立讀取資源文件的對象
  2. 建立 ClassLoader 對象
  3. 設置應用的工做目錄
  4. 啓動相關的輔助類如:logger、realm、resources 等
  5. 修改啓動狀態,通知感興趣的觀察者(Web 應用的配置)
  6. 子容器的初始化
  7. 獲取 ServletContext 並設置必要的參數
  8. 初始化「load on startup」的 Servlet

Web 應用的初始化工做

Web 應用的初始化工做是在 ContextConfig 的 configureStart 方法中實現的,應用的初始化主要是要解析 web.xml 文件,這個文件描述了一個 Web 應用的關鍵信息,也是一個 Web 應用的入口。

Tomcat 首先會找 globalWebXml 這個文件的搜索路徑是在 engine 的工做目錄下尋找如下兩個文件中的任一個 org/apache/catalin/startup/NO_DEFAULT_XML 或 conf/web.xml。接着會找 hostWebXml 這個文件可能會在 System.getProperty("catalina.base")/conf/${EngineName}/${HostName}/web.xml.default,接着尋找應用的配置文件 examples/WEB-INF/web.xml。web.xml 文件中的各個配置項將會被解析成相應的屬性保存在 WebXml 對象中。若是當前應用支持 Servlet3.0,解析還將完成額外 9 項工做,這個額外的 9 項工做主要是爲 Servlet3.0 新增的特性,包括 jar 包中的 META-INF/web-fragment.xml 的解析以及對 annotations 的支持。

接下去將會將 WebXml 對象中的屬性設置到 Context 容器中,這裏包括建立 Servlet 對象、filter、listener 等等。這段代碼在 WebXml 的 configureContext 方法中。下面是解析 Servlet 的代碼片斷:

清單 4. 建立 Wrapper 實例
 for (ServletDef servlet : servlets.values()) { 
            Wrapper wrapper = context.createWrapper(); 
            String jspFile = servlet.getJspFile(); 
            if (jspFile != null) { 
                wrapper.setJspFile(jspFile); 
            } 
            if (servlet.getLoadOnStartup() != null) { 
                wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue()); 
            } 
            if (servlet.getEnabled() != null) { 
                wrapper.setEnabled(servlet.getEnabled().booleanValue()); 
            } 
            wrapper.setName(servlet.getServletName()); 
            Map<String,String> params = servlet.getParameterMap(); 
            for (Entry<String, String> entry : params.entrySet()) { 
                wrapper.addInitParameter(entry.getKey(), entry.getValue()); 
            } 
            wrapper.setRunAs(servlet.getRunAs()); 
            Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs(); 
            for (SecurityRoleRef roleRef : roleRefs) { 
                wrapper.addSecurityReference( 
                        roleRef.getName(), roleRef.getLink()); 
            } 
            wrapper.setServletClass(servlet.getServletClass()); 
            MultipartDef multipartdef = servlet.getMultipartDef(); 
            if (multipartdef != null) { 
                if (multipartdef.getMaxFileSize() != null && 
                        multipartdef.getMaxRequestSize()!= null && 
                        multipartdef.getFileSizeThreshold() != null) { 
                    wrapper.setMultipartConfigElement(new 
 MultipartConfigElement( 
                            multipartdef.getLocation(), 
                            Long.parseLong(multipartdef.getMaxFileSize()), 
                            Long.parseLong(multipartdef.getMaxRequestSize()), 
                            Integer.parseInt( 
                                    multipartdef.getFileSizeThreshold()))); 
                } else { 
                    wrapper.setMultipartConfigElement(new 
 MultipartConfigElement( 
                            multipartdef.getLocation())); 
                } 
            } 
            if (servlet.getAsyncSupported() != null) { 
                wrapper.setAsyncSupported( 
                        servlet.getAsyncSupported().booleanValue()); 
            } 
            context.addChild(wrapper); 
 }

這段代碼清楚的描述瞭如何將 Servlet 包裝成 Context 容器中的 StandardWrapper,這裏有個疑問,爲何要將 Servlet 包裝成 StandardWrapper 而不直接是 Servlet 對象。這裏 StandardWrapper 是 Tomcat 容器中的一部分,它具備容器的特徵,而 Servlet 爲了一個獨立的 web 開發標準,不該該強耦合在 Tomcat 中。

除了將 Servlet 包裝成 StandardWrapper 並做爲子容器添加到 Context 中,其它的全部 web.xml 屬性都被解析到 Context 中,因此說 Context 容器纔是真正運行 Servlet 的 Servlet 容器。一個 Web 應用對應一個 Context 容器,容器的配置屬性由應用的 web.xml 指定,這樣咱們就能理解 web.xml 到底起到什麼做用了。

建立 Servlet 實例

前面已經完成了 Servlet 的解析工做,而且被包裝成 StandardWrapper 添加在 Context 容器中,可是它仍然不能爲咱們工做,它尚未被實例化。下面咱們將介紹 Servlet 對象是如何建立的,以及如何被初始化的。

建立 Servlet 對象

若是 Servlet 的 load-on-startup 配置項大於 0,那麼在 Context 容器啓動的時候就會被實例化,前面提到在解析配置文件時會讀取默認的 globalWebXml,在 conf 下的 web.xml 文件中定義了一些默認的配置項,其定義了兩個 Servlet,分別是:org.apache.catalina.servlets.DefaultServlet 和 org.apache.jasper.servlet.JspServlet 它們的 load-on-startup 分別是 1 和 3,也就是當 Tomcat 啓動時這兩個 Servlet 就會被啓動。

建立 Servlet 實例的方法是從 Wrapper. loadServlet 開始的。loadServlet 方法要完成的就是獲取 servletClass 而後把它交給 InstanceManager 去建立一個基於 servletClass.class 的對象。若是這個 Servlet 配置了 jsp-file,那麼這個 servletClass 就是 conf/web.xml 中定義的 org.apache.jasper.servlet.JspServlet 了。

建立 Servlet 對象的相關類結構圖以下:

圖 3. 建立 Servlet 對象的相關類結構

圖 3. 建立 Servlet 對象的相關類結構

初始化 Servlet

初始化 Servlet 在 StandardWrapper 的 initServlet 方法中,這個方法很簡單就是調用 Servlet 的 init 的方法,同時把包裝了 StandardWrapper 對象的 StandardWrapperFacade 做爲 ServletConfig 傳給 Servlet。Tomcat 容器爲什麼要傳 StandardWrapperFacade 給 Servlet 對象將在後面作詳細解析。

若是該 Servlet 關聯的是一個 jsp 文件,那麼前面初始化的就是 JspServlet,接下去會模擬一次簡單請求,請求調用這個 jsp 文件,以便編譯這個 jsp 文件爲 class,並初始化這個 class。

這樣 Servlet 對象就初始化完成了,事實上 Servlet 從被 web.xml 中解析到完成初始化,這個過程很是複雜,中間有不少過程,包括各類容器狀態的轉化引發的監聽事件的觸發、各類訪問權限的控制和一些不可預料的錯誤發生的判斷行爲等等。咱們這裏只抓了一些關鍵環節進行闡述,試圖讓你們有個整體脈絡。

下面是這個過程的一個完整的時序圖,其中也省略了一些細節。

圖 4. 初始化 Servlet 的時序圖(查看大圖

圖 4. 初始化 Servlet 的時序圖

Servlet 體系結構

咱們知道 Java Web 應用是基於 Servlet 規範運轉的,那麼 Servlet 自己又是如何運轉的呢?爲什麼要設計這樣的體系結構。

圖 5.Servlet 頂層類關聯圖

圖 5.Servlet 頂層類關聯圖

從上圖能夠看出 Servlet 規範就是基於這幾個類運轉的,與 Servlet 主動關聯的是三個類,分別是 ServletConfig、ServletRequest 和 ServletResponse。這三個類都是經過容器傳遞給 Servlet 的,其中 ServletConfig 是在 Servlet 初始化時就傳給 Servlet 了,然後兩個是在請求達到時調用 Servlet 時傳遞過來的。咱們很清楚 ServletRequest 和 ServletResponse 在 Servlet 運行的意義,可是 ServletConfig 和 ServletContext 對 Servlet 有何價值?仔細查看 ServletConfig 接口中聲明的方法發現,這些方法都是爲了獲取這個 Servlet 的一些配置屬性,而這些配置屬性可能在 Servlet 運行時被用到。而 ServletContext 又是幹什麼的呢? Servlet 的運行模式是一個典型的「握手型的交互式」運行模式。所謂「握手型的交互式」就是兩個模塊爲了交換數據一般都會準備一個交易場景,這個場景一直跟隨個這個交易過程直到這個交易完成爲止。這個交易場景的初始化是根據此次交易對象指定的參數來定製的,這些指定參數一般就會是一個配置類。因此對號入座,交易場景就由 ServletContext 來描述,而定製的參數集合就由 ServletConfig 來描述。而 ServletRequest 和 ServletResponse 就是要交互的具體對象了,它們一般都是做爲運輸工具來傳遞交互結果。

ServletConfig 是在 Servlet init 時由容器傳過來的,那麼 ServletConfig 究竟是個什麼對象呢?

下圖是 ServletConfig 和 ServletContext 在 Tomcat 容器中的類關係圖。

圖 6. ServletConfig 在容器中的類關聯圖

圖 6. ServletConfig 在容器中的類關聯圖

上圖能夠看出 StandardWrapper 和 StandardWrapperFacade 都實現了 ServletConfig 接口,而 StandardWrapperFacade 是 StandardWrapper 門面類。因此傳給 Servlet 的是 StandardWrapperFacade 對象,這個類可以保證從 StandardWrapper 中拿到 ServletConfig 所規定的數據,而又不把 ServletConfig 不關心的數據暴露給 Servlet。

一樣 ServletContext 也與 ServletConfig 有相似的結構,Servlet 中能拿到的 ServletContext 的實際對象也是 ApplicationContextFacade 對象。ApplicationContextFacade 一樣保證 ServletContex 只能從容器中拿到它該拿的數據,它們都起到對數據的封裝做用,它們使用的都是門面設計模式。

經過 ServletContext 能夠拿到 Context 容器中一些必要信息,好比應用的工做路徑,容器支持的 Servlet 最小版本等。

Servlet 中定義的兩個 ServletRequest 和 ServletResponse 它們實際的對象又是什麼呢?,咱們在建立本身的 Servlet 類時一般使用的都是 HttpServletRequest 和 HttpServletResponse,它們繼承了 ServletRequest 和 ServletResponse。爲什麼 Context 容器傳過來的 ServletRequest、ServletResponse 能夠被轉化爲 HttpServletRequest 和 HttpServletResponse 呢?

圖 7.Request 相關類結構圖

圖 7.Request 相關類結構圖

上圖是 Tomcat 建立的 Request 和 Response 的類結構圖。Tomcat 一接受到請求首先將會建立 org.apache.coyote.Request 和 org.apache.coyote.Response,這兩個類是 Tomcat 內部使用的描述一次請求和相應的信息類它們是一個輕量級的類,它們做用就是在服務器接收到請求後,通過簡單解析將這個請求快速的分配給後續線程去處理,因此它們的對象很小,很容易被 JVM 回收。接下去當交給一個用戶線程去處理這個請求時又建立 org.apache.catalina.connector. Request 和 org.apache.catalina.connector. Response 對象。這兩個對象一直穿越整個 Servlet 容器直到要傳給 Servlet,傳給 Servlet 的是 Request 和 Response 的門面類 RequestFacade 和 RequestFacade,這裏使用門面模式與前面同樣都是基於一樣的目的——封裝容器中的數據。一次請求對應的 Request 和 Response 的類轉化以下圖所示:

圖 8.Request 和 Response 的轉變過程

圖 8.Request 和 Response 的轉變過程

Servlet 如何工做

咱們已經清楚了 Servlet 是如何被加載的、Servlet 是如何被初始化的,以及 Servlet 的體系結構,如今的問題就是它是如何被調用的。

當用戶從瀏覽器向服務器發起一個請求,一般會包含以下信息:http://hostname: port /contextpath/servletpath,hostname 和 port 是用來與服務器創建 TCP 鏈接,然後面的 URL 纔是用來選擇服務器中那個子容器服務用戶的請求。那服務器是如何根據這個 URL 來達到正確的 Servlet 容器中的呢?

Tomcat7.0 中這件事很容易解決,由於這種映射工做有專門一個類來完成的,這個就是 org.apache.tomcat.util.http.mapper,這個類保存了 Tomcat 的 Container 容器中的全部子容器的信息,當 org.apache.catalina.connector. Request 類在進入 Container 容器以前,mapper 將會根據此次請求的 hostnane 和 contextpath 將 host 和 context 容器設置到 Request 的 mappingData 屬性中。因此當 Request 進入 Container 容器以前,它要訪問那個子容器這時就已經肯定了。

圖 9.Request 的 Mapper 類關係圖

圖 9.Request 的 Mapper 類關係圖

可能你有疑問,mapper 中怎麼會有容器的完整關係,這要回到圖 2 中 19 步 MapperListener 類的初始化過程,下面是 MapperListener 的 init 方法代碼 :

清單 5. MapperListener.init
 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 類做爲一個監聽者加到整個 Container 容器中的每一個子容器中,這樣只要任何一個容器發生變化,MapperListener 都將會被通知,相應的保存容器關係的 MapperListener 的 mapper 屬性也會修改。for 循環中就是將 host 及下面的子容器註冊到 mapper 中。

圖 10.Request 在容器中的路由圖

圖 10.Request 在容器中的路由圖

上圖描述了一次 Request 請求是如何達到最終的 Wrapper 容器的,咱們現正知道了請求是如何達到正確的 Wrapper 容器,可是請求到達最終的 Servlet 還要完成一些步驟,必需要執行 Filter 鏈,以及要通知你在 web.xml 中定義的 listener。

接下去就要執行 Servlet 的 service 方法了,一般狀況下,咱們本身定義的 servlet 並非直接去實現 javax.servlet.servlet 接口,而是去繼承更簡單的 HttpServlet 類或者 GenericServlet 類,咱們能夠有選擇的覆蓋相應方法去實現咱們要完成的工做。

Servlet 的確已經可以幫咱們完成全部的工做了,可是如今的 web 應用不多有直接將交互所有頁面都用 servlet 來實現,而是採用更加高效的 MVC 框架來實現。這些 MVC 框架基本的原理都是將全部的請求都映射到一個 Servlet,而後去實現 service 方法,這個方法也就是 MVC 框架的入口。

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

Session 與 Cookie

前面咱們已經說明了 Servlet 如何被調用,咱們基於 Servlet 來構建應用程序,那麼咱們能從 Servlet 得到哪些數據信息呢?

Servlet 可以給咱們提供兩部分數據,一個是在 Servlet 初始化時調用 init 方法時設置的 ServletConfig,這個類基本上含有了 Servlet 自己和 Servlet 所運行的 Servlet 容器中的基本信息。根據前面的介紹 ServletConfig 的實際對象是 StandardWrapperFacade,到底能得到哪些容器信息能夠看看這類提供了哪些接口。還有一部分數據是由 ServletRequest 類提供,它的實際對象是 RequestFacade,從提供的方法中發現主要是描述此次請求的 HTTP 協議的信息。因此要掌握 Servlet 的工做方式必需要很清楚 HTTP 協議,若是你還不清楚趕忙去找一些參考資料。關於這一塊還有一個讓不少人迷惑的 Session 與 Cookie。

Session 與 Cookie 無論是對 Java Web 的熟練使用者仍是初學者來講都是一個使人頭疼的東西。Session 與 Cookie 的做用都是爲了保持訪問用戶與後端服務器的交互狀態。它們有各自的優勢也有各自的缺陷。然而具備諷刺意味的是它們優勢和它們的使用場景又是矛盾的,例如使用 Cookie 來傳遞信息時,隨着 Cookie 個數的增多和訪問量的增長,它佔用的網絡帶寬也很大,試想假如 Cookie 佔用 200 個字節,若是一天的 PV 有幾億的時候,它要佔用多少帶寬。因此大訪問量的時候但願用 Session,可是 Session 的致命弱點是不容易在多臺服務器之間共享,因此這也限制了 Session 的使用。

無論 Session 和 Cookie 有什麼不足,咱們仍是要用它們。下面詳細講一下,Session 如何基於 Cookie 來工做。實際上有三種方式能可讓 Session 正常工做:

  1. 基於 URL Path Parameter,默認就支持
  2. 基於 Cookie,若是你沒有修改 Context 容器個 cookies 標識的話,默認也是支持的
  3. 基於 SSL,默認不支持,只有 connector.getAttribute("SSLEnabled") 爲 TRUE 時才支持

第一種狀況下,當瀏覽器不支持 Cookie 功能時,瀏覽器會將用戶的 SessionCookieName 重寫到用戶請求的 URL 參數中,它的傳遞格式如 /path/Servlet;name=value;name2=value2? Name3=value3,其中「Servlet;」後面的 K-V 對就是要傳遞的 Path Parameters,服務器會從這個 Path Parameters 中拿到用戶配置的 SessionCookieName。關於這個 SessionCookieName,若是你在 web.xml 中配置 session-config 配置項的話,其 cookie-config 下的 name 屬性就是這個 SessionCookieName 值,若是你沒有配置 session-config 配置項,默認的 SessionCookieName 就是你們熟悉的「JSESSIONID」。接着 Request 根據這個 SessionCookieName 到 Parameters 拿到 Session ID 並設置到 request.setRequestedSessionId 中。

請注意若是客戶端也支持 Cookie 的話,Tomcat 仍然會解析 Cookie 中的 Session ID,並會覆蓋 URL 中的 Session ID。

若是是第三種狀況的話將會根據 javax.servlet.request.ssl_session 屬性值設置 Session ID。

有了 Session ID 服務器端就能夠建立 HttpSession 對象了,第一次觸發是經過 request. getSession() 方法,若是當前的 Session ID 尚未對應的 HttpSession 對象那麼就建立一個新的,並將這個對象加到 org.apache.catalina. Manager 的 sessions 容器中保存,Manager 類將管理全部 Session 的生命週期,Session 過時將被回收,服務器關閉,Session 將被序列化到磁盤等。只要這個 HttpSession 對象存在,用戶就能夠根據 Session ID 來獲取到這個對象,也就達到了狀態的保持。

圖 11.Session 相關類圖

圖 11.Session 相關類圖

上從圖中能夠看出從 request.getSession 中獲取的 HttpSession 對象其實是 StandardSession 對象的門面對象,這與前面的 Request 和 Servlet 是同樣的原理。下圖是 Session 工做的時序圖:

圖 12.Session 工做的時序圖(查看大圖

圖 12.Session 工做的時序圖

還有一點與 Session 關聯的 Cookie 與其它 Cookie 沒有什麼不一樣,這個配置的配置能夠經過 web.xml 中的 session-config 配置項來指定。

Servlet 中的 Listener

整個 Tomcat 服務器中 Listener 使用的很是普遍,它是基於觀察者模式設計的,Listener 的設計對開發 Servlet 應用程序提供了一種快捷的手段,可以方便的從另外一個縱向維度控制程序和數據。目前 Servlet 中提供了 5 種兩類事件的觀察者接口,它們分別是:4 個 EventListeners 類型的,ServletContextAttributeListener、ServletRequestAttributeListener、ServletRequestListener、HttpSessionAttributeListener 和 2 個 LifecycleListeners 類型的,ServletContextListener、HttpSessionListener。以下圖所示:

圖 13.Servlet 中的 Listener(查看大圖

圖 13.Servlet 中的 Listener

它們基本上涵蓋了整個 Servlet 生命週期中,你感興趣的每種事件。這些 Listener 的實現類能夠配置在 web.xml 中的 <listener> 標籤中。固然也能夠在應用程序中動態添加 Listener,須要注意的是 ServletContextListener 在容器啓動以後就不能再添加新的,由於它所監聽的事件已經不會再出現。掌握這些 Listener 的使用,可以讓咱們的程序設計的更加靈活。

總結

本文涉及到內容有點多,要把每一個細節都說清楚,彷佛不可能,本文試着從 Servlet 容器的啓動到 Servlet 的初始化,以及 Servlet 的體系結構等這些環節中找出一些重點來說述,目的是能讀者有一個整體的完整的結構圖,同時也詳細分析了其中的一些難點問題,但願對你們有所幫助。

相關文章
相關標籤/搜索