国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
Servlet 工作原理解析 - 開源中國社區(qū)

Servlet 工作原理解析

到此頁底部 »

作 者 話 題 正 文

廣東 廣州

加入時間:2008-08-31
最近登錄:5分鐘前

分享代碼數(shù) 1012
討論區(qū)發(fā)帖 3990

14人關(guān)注此話題, 我要關(guān)注(收藏)(?) 回復此話題 1樓 發(fā)表于 2011-02-25 08:10 (3小時前), 5回 /149閱, 最后回復:1小時前

X相關(guān)的軟件

該文深入的介紹了 J2EE 中使用最廣泛的 Servlet 技術(shù)運行原理,不管對于初學者還是高手來說都非常值得一看。

從 Servlet 容器說起

要介紹 Servlet 必須要先把 Servlet 容器說清楚,Servlet 與 Servlet 容器的關(guān)系有點像槍和子彈的關(guān)系,槍是為子彈而生,而子彈又讓槍有了殺傷力。雖然它們是彼此依存的,但是又相互獨立發(fā)展,這一切都是為了適應(yīng)工業(yè)化生產(chǎn)的 結(jié)果。從技術(shù)角度來說是為了解耦,通過標準化接口來相互協(xié)作。既然接口是連接 Servlet 與 Servlet 容器的關(guān)鍵,那我們就從它們的接口說起。

前面說了 Servlet 容器作為一個獨立發(fā)展的標準化產(chǎn)品,目前它的種類很多,但是它們都有自己的市場定位,很難說誰優(yōu)誰劣,各有特點。例如現(xiàn)在比較流行的 Jetty,在定制化和移動領(lǐng)域有不錯的發(fā)展,我們這里還是以大家最為熟悉 Tomcat 為例來介紹 Servlet 容器如何管理 Servlet。Tomcat 本身也很復雜,我們只從 Servlet 與 Servlet 容器的接口部分開始介紹,關(guān)于 Tomcat 的詳細介紹可以參考我的另外一篇文章《 Tomcat 系統(tǒng)架構(gòu)與模式設(shè)計分析》。

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


圖 1 . Tomcat 容器模型

從上圖可以看出 Tomcat 的容器分為四個等級,真正管理 Servlet 的容器是 Context 容器,一個 Context 對應(yīng)一個 Web 工程,在 Tomcat 的配置文件中可以很容易發(fā)現(xiàn)這一點,如下:


清單 1 Context 配置參數(shù)

				 
<Context path="/projectOne " docBase="D:\projects\projectOne"
reloadable="true" />

 

下面詳細介紹一下 Tomcat 解析 Context 容器的過程,包括如何構(gòu)建 Servlet 的過程。

Servlet 容器的啟動過程

Tomcat7 也開始支持嵌入式功能,增加了一個啟動類 org.apache.catalina.startup.Tomcat。創(chuàng)建一個實例對象并調(diào)用 start 方法就可以很容易啟動 Tomcat,我們還可以通過這個對象來增加和修改 Tomcat 的配置參數(shù),如可以動態(tài)增加 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 的代碼是創(chuàng)建一個 Tomcat 實例并新增一個 Web 應(yīng)用,然后啟動 Tomcat 并調(diào)用其中的一個 HelloWorldExample Servlet,看有沒有正確返回預期的數(shù)據(jù)。

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;
}

 

前面已經(jīng)介紹了一個 Web 應(yīng)用對應(yīng)一個 Context 容器,也就是 Servlet 運行時的 Servlet 容器,添加一個 Web 應(yīng)用時將會創(chuàng)建一個 StandardContext 容器,并且給這個 Context 容器設(shè)置必要的參數(shù),url 和 path 分別代表這個應(yīng)用在 Tomcat 中的訪問路徑和這個應(yīng)用實際的物理路徑,這個兩個參數(shù)與清單 1 中的兩個參數(shù)是一致的。其中最重要的一個配置是 ContextConfig,這個類將會負責整個 Web 應(yīng)用配置的解析工作,后面將會詳細介紹。最后將這個 Context 容器加到父容器 Host 中。

接下去將會調(diào)用 Tomcat 的 start 方法啟動 Tomcat,如果你清楚 Tomcat 的系統(tǒng)架構(gòu),你會容易理解 Tomcat 的啟動邏輯,Tomcat 的啟動邏輯是基于觀察者模式設(shè)計的,所有的容器都會繼承 Lifecycle 接口,它管理者容器的整個生命周期,所有容器的的修改和狀態(tài)的改變都會由它去通知已經(jīng)注冊的觀察者(Listener),關(guān)于這個設(shè)計模式可以參考《 Tomcat 的系統(tǒng)架構(gòu)與設(shè)計模式,第二部分:設(shè)計模式》。Tomcat 啟動的時序圖可以用圖 2 表示。


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

上圖描述了 Tomcat 啟動過程中,主要類之間的時序關(guān)系,下面我們將會重點關(guān)注添加 examples 應(yīng)用所對應(yīng)的 StandardContext 容器的啟動過程。

當 Context 容器初始化狀態(tài)設(shè)為 init 時,添加在 Contex 容器的 Listener 將會被調(diào)用。ContextConfig 繼承了 LifecycleListener 接口,它是在調(diào)用清單 3 時被加入到 StandardContext 容器中。ContextConfig 類會負責整個 Web 應(yīng)用的配置文件的解析工作。

ContextConfig 的 init 方法將會主要完成以下工作:

  1. 創(chuàng)建用于解析 xml 配置文件的 contextDigester 對象
  2. 讀取默認 context.xml 配置文件,如果存在解析它
  3. 讀取默認 Host 配置文件,如果存在解析它
  4. 讀取默認 Context 自身的配置文件,如果存在解析它
  5. 設(shè)置 Context 的 DocBase

ContextConfig 的 init 方法完成后,Context 容器的會執(zhí)行 startInternal 方法,這個方法啟動邏輯比較復雜,主要包括如下幾個部分:

  1. 創(chuàng)建讀取資源文件的對象
  2. 創(chuàng)建 ClassLoader 對象
  3. 設(shè)置應(yīng)用的工作目錄
  4. 啟動相關(guān)的輔助類如:logger、realm、resources 等
  5. 修改啟動狀態(tài),通知感興趣的觀察者(Web 應(yīng)用的配置)
  6. 子容器的初始化
  7. 獲取 ServletContext 并設(shè)置必要的參數(shù)
  8. 初始化“load on startup”的 Servlet

Web 應(yīng)用的初始化工作

Web 應(yīng)用的初始化工作是在 ContextConfig 的 configureStart 方法中實現(xiàn)的,應(yīng)用的初始化主要是要解析 web.xml 文件,這個文件描述了一個 Web 應(yīng)用的關(guān)鍵信息,也是一個 Web 應(yīng)用的入口。

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

接下去將會將 WebXml 對象中的屬性設(shè)置到 Context 容器中,這里包括創(chuàng)建 Servlet 對象、filter、listener 等等。這段代碼在 WebXml 的 configureContext 方法中。下面是解析 Servlet 的代碼片段:


清單 4. 創(chuàng)建 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 開發(fā)標準,不應(yīng)該強耦合在 Tomcat 中。

除了將 Servlet 包裝成 StandardWrapper 并作為子容器添加到 Context 中,其它的所有 web.xml 屬性都被解析到 Context 中,所以說 Context 容器才是真正運行 Servlet 的 Servlet 容器。一個 Web 應(yīng)用對應(yīng)一個 Context 容器,容器的配置屬性由應(yīng)用的 web.xml 指定,這樣我們就能理解 web.xml 到底起到什么作用了。

創(chuàng)建 Servlet 實例

前面已經(jīng)完成了 Servlet 的解析工作,并且被包裝成 StandardWrapper 添加在 Context 容器中,但是它仍然不能為我們工作,它還沒有被實例化。下面我們將介紹 Servlet 對象是如何創(chuàng)建的,以及如何被初始化的。

創(chuàng)建 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 就會被啟動。

創(chuàng)建 Servlet 實例的方法是從 Wrapper. loadServlet 開始的。loadServlet 方法要完成的就是獲取 servletClass 然后把它交給 InstanceManager 去創(chuàng)建一個基于 servletClass.class 的對象。如果這個 Servlet 配置了 jsp-file,那么這個 servletClass 就是 conf/web.xml 中定義的 org.apache.jasper.servlet.JspServlet 了。

創(chuàng)建 Servlet 對象的相關(guān)類結(jié)構(gòu)圖如下:


圖 3. 創(chuàng)建 Servlet 對象的相關(guān)類結(jié)構(gòu)

初始化 Servlet

初始化 Servlet 在 StandardWrapper 的 initServlet 方法中,這個方法很簡單就是調(diào)用 Servlet 的 init 的方法,同時把包裝了 StandardWrapper 對象的 StandardWrapperFacade 作為 ServletConfig 傳給 Servlet。Tomcat 容器為何要傳 StandardWrapperFacade 給 Servlet 對象將在后面做詳細解析。

如果該 Servlet 關(guān)聯(lián)的是一個 jsp 文件,那么前面初始化的就是 JspServlet,接下去會模擬一次簡單請求,請求調(diào)用這個 jsp 文件,以便編譯這個 jsp 文件為 class,并初始化這個 class。

這樣 Servlet 對象就初始化完成了,事實上 Servlet 從被 web.xml 中解析到完成初始化,這個過程非常復雜,中間有很多過程,包括各種容器狀態(tài)的轉(zhuǎn)化引起的監(jiān)聽事件的觸發(fā)、各種訪問權(quán)限的控制和一些不可預料的錯誤發(fā)生的判 斷行為等等。我們這里只抓了一些關(guān)鍵環(huán)節(jié)進行闡述,試圖讓大家有個總體脈絡(luò)。

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


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

Servlet 體系結(jié)構(gòu)

我們知道 Java Web 應(yīng)用是基于 Servlet 規(guī)范運轉(zhuǎn)的,那么 Servlet 本身又是如何運轉(zhuǎn)的呢?為何要設(shè)計這樣的體系結(jié)構(gòu)。


圖 5.Servlet 頂層類關(guān)聯(lián)圖

從上圖可以看出 Servlet 規(guī)范就是基于這幾個類運轉(zhuǎn)的,與 Servlet 主動關(guān)聯(lián)的是三個類,分別是 ServletConfig、ServletRequest 和 ServletResponse。這三個類都是通過容器傳遞給 Servlet 的,其中 ServletConfig 是在 Servlet 初始化時就傳給 Servlet 了,而后兩個是在請求達到時調(diào)用 Servlet 時傳遞過來的。我們很清楚 ServletRequest 和 ServletResponse 在 Servlet 運行的意義,但是 ServletConfig 和 ServletContext 對 Servlet 有何價值?仔細查看 ServletConfig 接口中聲明的方法發(fā)現(xiàn),這些方法都是為了獲取這個 Servlet 的一些配置屬性,而這些配置屬性可能在 Servlet 運行時被用到。而 ServletContext 又是干什么的呢? Servlet 的運行模式是一個典型的“握手型的交互式”運行模式。所謂“握手型的交互式”就是兩個模塊為了交換數(shù)據(jù)通常都會準備一個交易場景,這個場景一直跟隨個這個 交易過程直到這個交易完成為止。這個交易場景的初始化是根據(jù)這次交易對象指定的參數(shù)來定制的,這些指定參數(shù)通常就會是一個配置類。所以對號入座,交易場景 就由 ServletContext 來描述,而定制的參數(shù)集合就由 ServletConfig 來描述。而 ServletRequest 和 ServletResponse 就是要交互的具體對象了,它們通常都是作為運輸工具來傳遞交互結(jié)果。

ServletConfig 是在 Servlet init 時由容器傳過來的,那么 ServletConfig 到底是個什么對象呢?

下圖是 ServletConfig 和 ServletContext 在 Tomcat 容器中的類關(guān)系圖。


圖 6. ServletConfig 在容器中的類關(guān)聯(lián)圖

上圖可以看出 StandardWrapper 和 StandardWrapperFacade 都實現(xiàn)了 ServletConfig 接口,而 StandardWrapperFacade 是 StandardWrapper 門面類。所以傳給 Servlet 的是 StandardWrapperFacade 對象,這個類能夠保證從 StandardWrapper 中拿到 ServletConfig 所規(guī)定的數(shù)據(jù),而又不把 ServletConfig 不關(guān)心的數(shù)據(jù)暴露給 Servlet。

同樣 ServletContext 也與 ServletConfig 有類似的結(jié)構(gòu),Servlet 中能拿到的 ServletContext 的實際對象也是 ApplicationContextFacade 對象。ApplicationContextFacade 同樣保證 ServletContex 只能從容器中拿到它該拿的數(shù)據(jù),它們都起到對數(shù)據(jù)的封裝作用,它們使用的都是門面設(shè)計模式。

通過 ServletContext 可以拿到 Context 容器中一些必要信息,比如應(yīng)用的工作路徑,容器支持的 Servlet 最小版本等。

Servlet 中定義的兩個 ServletRequest 和 ServletResponse 它們實際的對象又是什么呢?,我們在創(chuàng)建自己的 Servlet 類時通常使用的都是 HttpServletRequest 和 HttpServletResponse,它們繼承了 ServletRequest 和 ServletResponse。為何 Context 容器傳過來的 ServletRequest、ServletResponse 可以被轉(zhuǎn)化為 HttpServletRequest 和 HttpServletResponse 呢?


圖 7.Request 相關(guān)類結(jié)構(gòu)圖

上圖是 Tomcat 創(chuàng)建的 Request 和 Response 的類結(jié)構(gòu)圖。Tomcat 一接受到請求首先將會創(chuàng)建 org.apache.coyote.Request 和 org.apache.coyote.Response,這兩個類是 Tomcat 內(nèi)部使用的描述一次請求和相應(yīng)的信息類它們是一個輕量級的類,它們作用就是在服務(wù)器接收到請求后,經(jīng)過簡單解析將這個請求快速的分配給后續(xù)線程去處理,所 以它們的對象很小,很容易被 JVM 回收。接下去當交給一個用戶線程去處理這個請求時又創(chuàng)建 org.apache.catalina.connector. Request 和 org.apache.catalina.connector. Response 對象。這兩個對象一直穿越整個 Servlet 容器直到要傳給 Servlet,傳給 Servlet 的是 Request 和 Response 的門面類 RequestFacade 和 RequestFacade,這里使用門面模式與前面一樣都是基于同樣的目的——封裝容器中的數(shù)據(jù)。一次請求對應(yīng)的 Request 和 Response 的類轉(zhuǎn)化如下圖所示:


圖 8.Request 和 Response 的轉(zhuǎn)變過程

Servlet 如何工作

我們已經(jīng)清楚了 Servlet 是如何被加載的、Servlet 是如何被初始化的,以及 Servlet 的體系結(jié)構(gòu),現(xiàn)在的問題就是它是如何被調(diào)用的。

當用戶從瀏覽器向服務(wù)器發(fā)起一個請求,通常會包含如下信息:http://hostname: port /contextpath/servletpath,hostname 和 port 是用來與服務(wù)器建立 TCP 連接,而后面的 URL 才是用來選擇服務(wù)器中那個子容器服務(wù)用戶的請求。那服務(wù)器是如何根據(jù)這個 URL 來達到正確的 Servlet 容器中的呢?

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


圖 9.Request 的 Mapper 類關(guān)系圖

可能你有疑問,mapper 中怎么會有容器的完整關(guān)系,這要回到圖 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 類作為一個監(jiān)聽者加到整個 Container 容器中的每個子容器中,這樣只要任何一個容器發(fā)生變化,MapperListener 都將會被通知,相應(yīng)的保存容器關(guān)系的 MapperListener 的 mapper 屬性也會修改。for 循環(huán)中就是將 host 及下面的子容器注冊到 mapper 中。


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

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

接下去就要執(zhí)行 Servlet 的 service 方法了,通常情況下,我們自己定義的 servlet 并不是直接去實現(xiàn) javax.servlet.servlet 接口,而是去繼承更簡單的 HttpServlet 類或者 GenericServlet 類,我們可以有選擇的覆蓋相應(yīng)方法去實現(xiàn)我們要完成的工作。

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

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

Session 與 Cookie

前面我們已經(jīng)說明了 Servlet 如何被調(diào)用,我們基于 Servlet 來構(gòu)建應(yīng)用程序,那么我們能從 Servlet 獲得哪些數(shù)據(jù)信息呢?

Servlet 能夠給我們提供兩部分數(shù)據(jù),一個是在 Servlet 初始化時調(diào)用 init 方法時設(shè)置的 ServletConfig,這個類基本上含有了 Servlet 本身和 Servlet 所運行的 Servlet 容器中的基本信息。根據(jù)前面的介紹 ServletConfig 的實際對象是 StandardWrapperFacade,到底能獲得哪些容器信息可以看看這類提供了哪些接口。還有一部分數(shù)據(jù)是由 ServletRequest 類提供,它的實際對象是 RequestFacade,從提供的方法中發(fā)現(xiàn)主要是描述這次請求的 HTTP 協(xié)議的信息。所以要掌握 Servlet 的工作方式必須要很清楚 HTTP 協(xié)議,如果你還不清楚趕緊去找一些參考資料。關(guān)于這一塊還有一個讓很多人迷惑的 Session 與 Cookie。

Session 與 Cookie 不管是對 Java Web 的熟練使用者還是初學者來說都是一個令人頭疼的東西。Session 與 Cookie 的作用都是為了保持訪問用戶與后端服務(wù)器的交互狀態(tài)。它們有各自的優(yōu)點也有各自的缺陷。然而具有諷刺意味的是它們優(yōu)點和它們的使用場景又是矛盾的,例如使 用 Cookie 來傳遞信息時,隨著 Cookie 個數(shù)的增多和訪問量的增加,它占用的網(wǎng)絡(luò)帶寬也很大,試想假如 Cookie 占用 200 個字節(jié),如果一天的 PV 有幾億的時候,它要占用多少帶寬。所以大訪問量的時候希望用 Session,但是 Session 的致命弱點是不容易在多臺服務(wù)器之間共享,所以這也限制了 Session 的使用。

不管 Session 和 Cookie 有什么不足,我們還是要用它們。下面詳細講一下,Session 如何基于 Cookie 來工作。實際上有三種方式能可以讓 Session 正常工作:

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

第一種情況下,當瀏覽器不支持 Cookie 功能時,瀏覽器會將用戶的 SessionCookieName 重寫到用戶請求的 URL 參數(shù)中,它的傳遞格式如 /path/Servlet;name=value;name2=value2? Name3=value3,其中“Servlet;”后面的 K-V 對就是要傳遞的 Path Parameters,服務(wù)器會從這個 Path Parameters 中拿到用戶配置的 SessionCookieName。關(guān)于這個 SessionCookieName,如果你在 web.xml 中配置 session-config 配置項的話,其 cookie-config 下的 name 屬性就是這個 SessionCookieName 值,如果你沒有配置 session-config 配置項,默認的 SessionCookieName 就是大家熟悉的“JSESSIONID”。接著 Request 根據(jù)這個 SessionCookieName 到 Parameters 拿到 Session ID 并設(shè)置到 request.setRequestedSessionId 中。

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

如果是第三種情況的話將會根據(jù) javax.servlet.request.ssl_session 屬性值設(shè)置 Session ID。

有了 Session ID 服務(wù)器端就可以創(chuàng)建 HttpSession 對象了,第一次觸發(fā)是通過 request. getSession() 方法,如果當前的 Session ID 還沒有對應(yīng)的 HttpSession 對象那么就創(chuàng)建一個新的,并將這個對象加到 org.apache.catalina. Manager 的 sessions 容器中保存,Manager 類將管理所有 Session 的生命周期,Session 過期將被回收,服務(wù)器關(guān)閉,Session 將被序列化到磁盤等。只要這個 HttpSession 對象存在,用戶就可以根據(jù) Session ID 來獲取到這個對象,也就達到了狀態(tài)的保持。


圖 11.Session 相關(guān)類圖

上從圖中可以看出從 request.getSession 中獲取的 HttpSession 對象實際上是 StandardSession 對象的門面對象,這與前面的 Request 和 Servlet 是一樣的原理。下圖是 Session 工作的時序圖:


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

還有一點與 Session 關(guān)聯(lián)的 Cookie 與其它 Cookie 沒有什么不同,這個配置的配置可以通過 web.xml 中的 session-config 配置項來指定。

Servlet 中的 Listener

整個 Tomcat 服務(wù)器中 Listener 使用的非常廣泛,它是基于觀察者模式設(shè)計的,Listener 的設(shè)計對開發(fā) Servlet 應(yīng)用程序提供了一種快捷的手段,能夠方便的從另一個縱向維度控制程序和數(shù)據(jù)。目前 Servlet 中提供了 5 種兩類事件的觀察者接口,它們分別是:4 個 EventListeners 類型的,ServletContextAttributeListener、ServletRequestAttributeListener、 ServletRequestListener、HttpSessionAttributeListener 和 2 個 LifecycleListeners 類型的,ServletContextListener、HttpSessionListener。如下圖所示:


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

它們基本上涵蓋了整個 Servlet 生命周期中,你感興趣的每種事件。這些 Listener 的實現(xiàn)類可以配置在 web.xml 中的 <listener> 標簽中。當然也可以在應(yīng)用程序中動態(tài)添加 Listener,需要注意的是 ServletContextListener 在容器啟動之后就不能再添加新的,因為它所監(jiān)聽的事件已經(jīng)不會再出現(xiàn)。掌握這些 Listener 的使用,能夠讓我們的程序設(shè)計的更加靈活。

總結(jié)

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

更多關(guān)于J2EE的詳細信息,或者下載地址請點這里

本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Servlet 工作原理解析
TOMCAT源碼分析(消息處理)
servlet學習總結(jié)
Tomcat工作原理C
Tomcat 系統(tǒng)架構(gòu)與設(shè)計模式,第 1 部分: 工作原理
編程語言學習Tomcat(四)之Engine和Host容器
更多類似文章 >>
生活服務(wù)
分享 收藏 導長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服