首頁  >  文章  >  Java  >  Servlet 工作原理解析

Servlet 工作原理解析

巴扎黑
巴扎黑原創
2017-04-30 10:01:161104瀏覽

  Web 技術成為當今主流的互聯網 Web 應用技術之一,而 Servlet 是 Java Web 技術的核心基礎。因而掌握 Servlet 的工作原理是成為合格的 Java Web 技術開發人員的基本要求。本文將帶你認識 Java Web 技術是如何基於 Servlet 工作,你將知道:以 Tomcat 為例了解 Servlet 容器是如何運作的?一個 Web 工程在 Servlet 容器中是如何啟動的? Servlet 容器如何解析你在 web.xml 中定義的 Servlet ?使用者的請求是如何被分配給指定的 Servlet 的? Servlet 容器如何管理 Servlet 生命週期?你也將了解最新的 Servlet 的 API 的類別層次結構,以及 Servlet 中一些難點問題的分析。

#  從 Servlet 容器說起

  要介紹 Servlet 必須要先把 Servlet 容器說清楚,Servlet 與 Servlet 容器的關係有點像槍和子彈的關係,槍是為子彈而生,而子彈又讓槍有了殺傷力。雖然它們是彼此依存的,但又相互獨立發展,這一切都是為了適應工業化生產的結果。從技術角度來說是為了解耦,透過標準化介面來相互協作。既然介面是連接 Servlet 與 Servlet 容器的關鍵,那我們就從它們的介面說起。

  前面說了 Servlet 容器作為一個獨立發展的標準化產品,目前它的種類很多,但是它們都有自己的市場定位,很難說誰優誰劣,各有特點。例如現在比較流行的 Jetty,在客製化和行動領域有不錯的發展,我們這裡還是以大家最為熟悉 Tomcat 為例來介紹 Servlet 容器如何管理 Servlet。 Tomcat 本身也很複雜,我們只從 Servlet 與 Servlet 容器的介面部分開始介紹,關於 Tomcat 的詳細介紹可以參考我的另外一篇文章《 Tomcat 系統架構與模式設計分析》。

  Tomcat 的容器等級中,Context 容器是直接管理 Servlet 在容器中的包裝類別 Wrapper,所以 Context 容器如何運作將直接影響 Servlet 的工作方式。

  圖 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 的 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 中。

  接下去将会调用 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、HttpSessionAttributeList如下圖所示:

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

图 13.Servlet 中的 Listener

#   它們基本上涵蓋了整個 Servlet 生命週期中,你感興趣的每種事件。這些 Listener 的實作類別可以配置在 web.xml 中的 eae7f28921fa176b20e23abb843be090 標籤中。當然也可以在應用程式中動態加入 Listener,需要注意的是 ServletContextListener 在容器啟動之後就不能再增加新的,因為它所監聽的事件已經不會再出現。掌握這些 Listener 的使用,能夠讓我們的程式設計的更靈活。


#  總結

本文涉及到內容有點多,要把每個細節都說清楚,似乎不可能,本文試著從Servlet 容器的啟動到Servlet 的初始化,以及Servlet 的體系結構等這些環節中找出一些重點來講述,目的是能讀者有一個整體的完整的結構圖,同時也詳細分析了其中的一些難點問題,希望對大家有幫助。

以上是Servlet 工作原理解析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn