新聞中心
本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內(nèi)容請到我的倉庫里查看
創(chuàng)新互聯(lián)公司專注于企業(yè)營銷型網(wǎng)站建設(shè)、網(wǎng)站重做改版、郟縣網(wǎng)站定制設(shè)計、自適應(yīng)品牌網(wǎng)站建設(shè)、html5、商城開發(fā)、集團公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站建設(shè)公司、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁設(shè)計等建站業(yè)務(wù),價格優(yōu)惠性價比高,為郟縣等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。
https://github.com/h3pl/Java-Tutorial
喜歡的話麻煩點下Star哈
文章首發(fā)于我的個人博客:
www.how2playlife.com
本文是微信公眾號【Java技術(shù)江湖】的《走進JavaWeb技術(shù)世界》其中一篇,本文部分內(nèi)容來源于網(wǎng)絡(luò),為了把本文主題講得清晰透徹,也整合了很多我認(rèn)為不錯的技術(shù)博客內(nèi)容,引用其中了一些比較好的博客文章,如有侵權(quán),請聯(lián)系作者。
該系列博文會告訴你如何從入門到進階,從servlet到框架,從ssm再到SpringBoot,一步步地學(xué)習(xí)JavaWeb基礎(chǔ)知識,并上手進行實戰(zhàn),接著了解JavaWeb項目中經(jīng)常要使用的技術(shù)和組件,包括日志組件、Maven、Junit,等等內(nèi)容,以便讓你更完整地了解整個Java Web技術(shù)體系,形成自己的知識框架。
為了更好地總結(jié)和檢驗?zāi)愕膶W(xué)習(xí)成果,本系列文章也會提供每個知識點對應(yīng)的面試題以及參考答案。
如果對本系列文章有什么建議,或者是有什么疑問的話,也可以關(guān)注公眾號【Java技術(shù)江湖】聯(lián)系作者,歡迎你參與本系列博文的創(chuàng)作和修訂。
什么是Servlet
Servlet的作用是為Java程序提供一個統(tǒng)一的web應(yīng)用的規(guī)范,方便程序員統(tǒng)一的使用這種規(guī)范來編寫程序,應(yīng)用容器可以使用提供的規(guī)范來實現(xiàn)自己的特性。比如tomcat的代碼和jetty的代碼就不一樣,但作為程序員你只需要了解servlet規(guī)范就可以從request中取值,你可以操作session等等。不用在意應(yīng)用服務(wù)器底層的實現(xiàn)的差別而影響你的開發(fā)。
HTTP 協(xié)議只是一個規(guī)范,定義服務(wù)請求和響應(yīng)的大致式樣。Java servlet 類將HTTP中那些低層的結(jié)構(gòu)包裝在 Java 類中,這些類所包含的便利方法使其在 Java 語言環(huán)境中更易于處理。
正如您正使用的特定 servlet 容器的配置文件中所定義的,當(dāng)用戶通過 URL 發(fā)出一個請求時,這些 Java servlet 類就將之轉(zhuǎn)換成一個 HttpServletRequest,并發(fā)送給 URL 所指向的目標(biāo)。當(dāng)服務(wù)器端完成其工作時,Java 運行時環(huán)境(Java Runtime Environment)就將結(jié)果包裝在一個 HttpServletResponse 中,然后將原 HTTP 響應(yīng)送回給發(fā)出該請求的客戶機。在與 Web 應(yīng)用程序進行交互時,通常會發(fā)出多個請求并獲得多個響應(yīng)。所有這些都是在一個會話語境中,Java 語言將之包裝在一個 HttpSession 對象中。在處理響應(yīng)時,您可以訪問該對象,并在創(chuàng)建響應(yīng)時向其添加事件。它提供了一些跨請求的語境。
容器(如 Tomcat)將為 servlet 管理運行時環(huán)境。您可以配置該容器,定制 J2EE 服務(wù)器的工作方式,以便將 servlet 暴露給外部世界。正如我們將看到的,通過該容器中的各種配置文件,您在 URL(由用戶在瀏覽器中輸入)與服務(wù)器端組件之間搭建了一座橋梁,這些組件將處理您需要該 URL 轉(zhuǎn)換的請求。在運行應(yīng)用程序時,該容器將加載并初始化 servlet,管理其生命周期。
Servlet體系結(jié)構(gòu)

Servlet頂級類關(guān)聯(lián)圖
Servlet
Servlet的框架是由兩個Java包組成的:javax.servlet與javax.servlet.http。在javax.servlet包中定義了所有的Servlet類都必須實現(xiàn)或者擴展的通用接口和類。在javax.servlet.http包中定義了采用Http協(xié)議通信的HttpServlet類。Servlet的框架的核心是javax.servlet.Servlet接口,所有的Servlet都必須實現(xiàn)這個接口。

Servlet接口
在Servlet接口中定義了5個方法:
1\. init(ServletConfig)方法:負(fù)責(zé)初始化Servlet對象,在Servlet的生命周期中,該方法執(zhí)行一次;該方法執(zhí)行在單線程的環(huán)境下,因此開發(fā)者不用考慮線程安全的問題;
2\. service(ServletRequest req,ServletResponse res)方法:負(fù)責(zé)響應(yīng)客戶的請求;為了提高效率,Servlet規(guī)范要求一個Servlet實例必須能夠同時服務(wù)于多個客戶端請求,即service()方法運行在多線程的環(huán)境下,Servlet開發(fā)者必須保證該方法的線程安全性;
3\. destroy()方法:當(dāng)Servlet對象退出生命周期時,負(fù)責(zé)釋放占用的資源;
4\. getServletInfo:就是字面意思,返回Servlet的描述;
5\. getServletConfig:這個方法返回由Servlet容器傳給init方法的ServletConfig。
ServletRequest & ServletResponse
對于每一個HTTP請求,servlet容器會創(chuàng)建一個封裝了HTTP請求的ServletRequest實例傳遞給servlet的service方法,ServletResponse則表示一個Servlet響應(yīng),其隱藏了將響應(yīng)發(fā)給瀏覽器的復(fù)雜性。通過ServletRequest的方法你可以獲取一些請求相關(guān)的參數(shù),而ServletResponse則可以將設(shè)置一些返回參數(shù)信息,并且設(shè)置返回內(nèi)容。
ServletConfig
ServletConfig封裝可以通過@WebServlet或者web.xml傳給一個Servlet的配置信息,以這種方式傳遞的每一條信息都稱做初始化信息,初始化信息就是一個個K-V鍵值對。為了從一個Servlet內(nèi)部獲取某個初始參數(shù)的值,init方法中調(diào)用ServletConfig的getinitParameter方法或getinitParameterNames方法獲取,除此之外,還可以通過getServletContext獲取ServletContext對象。
ServletContext
ServletContext是代表了Servlet應(yīng)用程序。每個Web應(yīng)用程序只有一個context。在分布式環(huán)境中,一個應(yīng)用程序同時部署到多個容器中,并且每臺Java虛擬機都有一個ServletContext對象。有了ServletContext對象后,就可以共享能通過應(yīng)用程序的所有資源訪問的信息,促進Web對象的動態(tài)注冊,共享的信息通過一個內(nèi)部Map中的對象保存在ServiceContext中來實現(xiàn)。保存在ServletContext中的對象稱作屬性。操作屬性的方法:
GenericServlet
前面編寫的Servlet應(yīng)用中通過實現(xiàn)Servlet接口來編寫Servlet,但是我們每次都必須為Servlet中的所有方法都提供實現(xiàn),還需要將ServletConfig對象保存到一個類級別的變量中,GenericServlet抽象類就是為了為我們省略一些模板代碼,實現(xiàn)了Servlet和ServletConfig,完成了一下幾個工作:
將init方法中的ServletConfig賦給一個類級變量,使的可以通過getServletConfig來獲取。
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
同時為避免覆蓋init方法后在子類中必須調(diào)用super.init(servletConfig),GenericServlet還提供了一個不帶參數(shù)的init方法,當(dāng)ServletConfig賦值完成就會被第帶參數(shù)的init方法調(diào)用。這樣就可以通過覆蓋不帶參數(shù)的init方法編寫初始化代碼,而ServletConfig實例依然得以保存
為Servlet接口中的所有方法提供默認(rèn)實現(xiàn)。
提供方法來包裝ServletConfig中的方法。
HTTPServlet
在編寫Servlet應(yīng)用程序時,大多數(shù)都要用到HTTP,也就是說可以利用HTTP提供的特性,javax.servlet.http包含了編寫Servlet應(yīng)用程序的類和接口,其中很多覆蓋了javax.servlet中的類型,我們自己在編寫應(yīng)用時大多時候也是繼承的HttpServlet。
Servlet工作原理
當(dāng)Web服務(wù)器接收到一個HTTP請求時,它會先判斷請求內(nèi)容——如果是靜態(tài)網(wǎng)頁數(shù)據(jù),Web服務(wù)器將會自行處理,然后產(chǎn)生響應(yīng)信息;如果牽涉到動態(tài)數(shù)據(jù),Web服務(wù)器會將請求轉(zhuǎn)交給Servlet容器。此時Servlet容器會找到對應(yīng)的處理該請求的Servlet實例來處理,結(jié)果會送回Web服務(wù)器,再由Web服務(wù)器傳回用戶端。
針對同一個Servlet,Servlet容器會在第一次收到http請求時建立一個Servlet實例,然后啟動一個線程。第二次收到http請求時,Servlet容器無須建立相同的Servlet實例,而是啟動第二個線程來服務(wù)客戶端請求。所以多線程方式不但可以提高Web應(yīng)用程序的執(zhí)行效率,也可以降低Web服務(wù)器的系統(tǒng)負(fù)擔(dān)。

Web服務(wù)器工作流程
接著我們描述一下Tomcat與Servlet是如何工作的,首先看下面的時序圖:

Servlet工作原理時序圖
Web Client 向Servlet容器(Tomcat)發(fā)出Http請求;
Servlet容器接收Web Client的請求;
Servlet容器創(chuàng)建一個HttpRequest對象,將Web Client請求的信息封裝到這個對象中;
Servlet容器創(chuàng)建一個HttpResponse對象;
Servlet容器調(diào)用HttpServlet對象的service方法,把HttpRequest對象與HttpResponse對象作為參數(shù)傳給 HttpServlet對象;
HttpServlet調(diào)用HttpRequest對象的有關(guān)方法,獲取Http請求信息;
HttpServlet調(diào)用HttpResponse對象的有關(guān)方法,生成響應(yīng)數(shù)據(jù);
- Servlet容器把HttpServlet的響應(yīng)結(jié)果傳給Web Client;
Servlet生命周期
在Servlet接口中定義了5個方法,其中3個方法代表了Servlet的生命周期:
1\. init(ServletConfig)方法:負(fù)責(zé)初始化Servlet對象,在Servlet的生命周期中,該方法執(zhí)行一次;該方法執(zhí)行在單線程的環(huán)境下,因此開發(fā)者不用考慮線程安全的問題;
2\. service(ServletRequest req,ServletResponse res)方法:負(fù)責(zé)響應(yīng)客戶的請求;為了提高效率,Servlet規(guī)范要求一個Servlet實例必須能夠同時服務(wù)于多個客戶端請求,即service()方法運行在多線程的環(huán)境下,Servlet開發(fā)者必須保證該方法的線程安全性;
3\. destroy()方法:當(dāng)Servlet對象退出生命周期時,負(fù)責(zé)釋放占用的資源;
編程注意事項說明:
- 當(dāng)Server Thread線程執(zhí)行Servlet實例的init()方法時,所有的Client Service Thread線程都不能執(zhí)行該實例的service()方法,更沒有線程能夠執(zhí)行該實例的destroy()方法,因此Servlet的init()方法是工作在單線程的環(huán)境下,開發(fā)者不必考慮任何線程安全的問題。
- 當(dāng)服務(wù)器接收到來自客戶端的多個請求時,服務(wù)器會在單獨的Client Service Thread線程中執(zhí)行Servlet實例的service()方法服務(wù)于每個客戶端。此時會有多個線程同時執(zhí)行同一個Servlet實例的service()方法,因此必須考慮線程安全的問題。
- 雖然service()方法運行在多線程的環(huán)境下,并不一定要同步該方法。而是要看這個方法在執(zhí)行過程中訪問的資源類型及對資源的訪問方式。分析如下:
1\. 如果service()方法沒有訪問Servlet的成員變量也沒有訪問全局的資源比如靜態(tài)變量、文件、數(shù)據(jù)庫連接等,而是只使用了當(dāng)前線程自己的資源,比如非指向全局資源的臨時變量、request和response對象等。該方法本身就是線程安全的,不必進行任何的同步控制。
2\. 如果service()方法訪問了Servlet的成員變量,但是對該變量的操作是只讀操作,該方法本身就是線程安全的,不必進行任何的同步控制。
3\. 如果service()方法訪問了Servlet的成員變量,并且對該變量的操作既有讀又有寫,通常需要加上同步控制語句。
4\. 如果service()方法訪問了全局的靜態(tài)變量,如果同一時刻系統(tǒng)中也可能有其它線程訪問該靜態(tài)變量,如果既有讀也有寫的操作,通常需要加上同步控制語句。
5\. 如果service()方法訪問了全局的資源,比如文件、數(shù)據(jù)庫連接等,通常需要加上同步控制語句。
在創(chuàng)建一個 Java servlet 時,一般需要子類 HttpServlet。該類中的方法允許您訪問請求和響應(yīng)包裝器(wrapper),您可以用這個包裝器來處理請求和創(chuàng)建響應(yīng)。Servlet的生命周期,簡單的概括這就分為四步:
Servlet類加載--->實例化--->服務(wù)--->銷毀;

Servlet生命周期
創(chuàng)建Servlet對象的時機:
- 默認(rèn)情況下,在Servlet容器啟動后:客戶首次向Servlet發(fā)出請求,Servlet容器會判斷內(nèi)存中是否存在指定的Servlet對象,如果沒有則創(chuàng)建它,然后根據(jù)客戶的請求創(chuàng)建HttpRequest、HttpResponse對象,從而調(diào)用Servlet對象的service方法;
- Servlet容器啟動時:當(dāng)web.xml文件中如果
元素中指定了 子元素時,Servlet容器在啟動web服務(wù)器時,將按照順序創(chuàng)建并初始化Servlet對象; - Servlet的類文件被更新后,重新創(chuàng)建Servlet。Servlet容器在啟動時自動創(chuàng)建Servlet,這是由在web.xml文件中為Servlet設(shè)置的
屬性決定的。從中我們也能看到同一個類型的Servlet對象在Servlet容器中以單例的形式存在;
注意:在web.xml文件中,某些Servlet只有
元素,沒有 元素,這樣我們無法通過url的方式訪問這些Servlet,這種Servlet通常會在 元素中配置一個 子元素,讓容器在啟動的時候自動加載這些Servlet并調(diào)用init(ServletConfig config)方法來初始化該Servlet。其中方法參數(shù)config中包含了Servlet的配置信息,比如初始化參數(shù),該對象由服務(wù)器創(chuàng)建。
銷毀Servlet對象的時機:
Servlet容器停止或者重新啟動:Servlet容器調(diào)用Servlet對象的destroy方法來釋放資源。以上所講的就是Servlet對象的生命周期。那么Servlet容器如何知道創(chuàng)建哪一個Servlet對象?Servlet對象如何配置?實際上這些信息是通過讀取web.xml配置文件來實現(xiàn)的。
action
org.apache.struts.action.ActionServlet
config
/WEB-INF/struts-config.xml
detail
2
debug
2
2
action
*.do
當(dāng)Servlet容器啟動的時候讀取
配置節(jié)信息,根據(jù) 配置節(jié)信息創(chuàng)建Servlet對象,同時根據(jù) 配置節(jié)信息創(chuàng)建HttpServletConfig對象,然后執(zhí)行Servlet對象的init方法,并且根據(jù) 配置節(jié)信息來決定創(chuàng)建Servlet對象的順序,如果此配置節(jié)信息為負(fù)數(shù)或者沒有配置,那么在Servlet容器啟動時,將不加載此Servlet對象。當(dāng)客戶訪問Servlet容器時,Servlet容器根據(jù)客戶訪問的URL地址,通過 配置節(jié)中的 配置節(jié)信息找到指定的Servlet對象,并調(diào)用此Servlet對象的service方法。
在整個Servlet的生命周期過程中,創(chuàng)建Servlet實例、調(diào)用實例的init()和destroy()方法都只進行一次,當(dāng)初始化完成后,Servlet容器會將該實例保存在內(nèi)存中,通過調(diào)用它的service()方法,為接收到的請求服務(wù)。下面給出Servlet整個生命周期過程的UML序列圖,如圖所示:

Servlet生命周期
如果需要讓Servlet容器在啟動時即加載Servlet,可以在web.xml文件中配置
元素。
Servlet中的Listener
Listener 使用的非常廣泛,它是基于觀察者模式設(shè)計的,Listener 的設(shè)計對開發(fā) Servlet 應(yīng)用程序提供了一種快捷的手段,能夠方便的從另一個縱向維度控制程序和數(shù)據(jù)。目前 Servlet 中提供了 5 種兩類事件的觀察者接口,它們分別是:4 個 EventListeners 類型的,ServletContextAttributeListener、ServletRequestAttributeListener、ServletRequestListener、HttpSessionAttributeListener 和 2 個 LifecycleListeners 類型的,ServletContextListener、HttpSessionListener。如下圖所示:

Servlet中的Listener
它們基本上涵蓋了整個 Servlet 生命周期中,你感興趣的每種事件。這些 Listener 的實現(xiàn)類可以配置在 web.xml 中的
Cookie與Session
Servlet 能夠給我們提供兩部分?jǐn)?shù)據(jù),一個是在 Servlet 初始化時調(diào)用 init 方法時設(shè)置的 ServletConfig,這個類基本上含有了 Servlet 本身和 Servlet 所運行的 Servlet 容器中的基本信息。還有一部分?jǐn)?shù)據(jù)是由 ServletRequest 類提供,從提供的方法中發(fā)現(xiàn)主要是描述這次請求的 HTTP 協(xié)議的信息。關(guān)于這一塊還有一個讓很多人迷惑的 Session 與 Cookie。
Session 與 Cookie 的作用都是為了保持訪問用戶與后端服務(wù)器的交互狀態(tài)。它們有各自的優(yōu)點也有各自的缺陷。然而具有諷刺意味的是它們優(yōu)點和它們的使用場景又是矛盾的,例如使用 Cookie 來傳遞信息時,隨著 Cookie 個數(shù)的增多和訪問量的增加,它占用的網(wǎng)絡(luò)帶寬也也會越來越大。所以大訪問量的時候希望用 Session,但是 Session 的致命弱點是不容易在多臺服務(wù)器之間共享,所以這也限制了 Session 的使用。
不管 Session 和 Cookie 有什么不足,我們還是要用它們。下面詳細(xì)講一下,Session 如何基于 Cookie 來工作。實際上有三種方式能可以讓 Session 正常工作:
- 基于 URL Path Parameter,默認(rèn)就支持
- 基于 Cookie,如果你沒有修改 Context 容器個 cookies 標(biāo)識的話,默認(rèn)也是支持的
- 基于 SSL,默認(rèn)不支持,只有 connector.getAttribute("SSLEnabled") 為 TRUE 時才支持
第一種情況下,當(dāng)瀏覽器不支持 Cookie 功能時,瀏覽器會將用戶的 SessionCookieName 重寫到用戶請求的 URL 參數(shù)中,它的傳遞格式如:
/path/Servlet?name=value&name2=value2&JSESSIONID=value3
接著 Request 根據(jù)這個 JSESSIONID 參數(shù)拿到 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() 方法,如果當(dāng)前的 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)的保持。

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

Session工作的時序圖
還有一點與 Session 關(guān)聯(lián)的 Cookie 與其它 Cookie 沒有什么不同,這個配置的配置可以通過 web.xml 中的 session-config 配置項來指定。
參考文章
https://blog.csdn.net/android_hl/article/details/53228348
當(dāng)前文章:走進JavaWeb技術(shù)世界4:Servlet工作原理詳解
轉(zhuǎn)載來源:http://fisionsoft.com.cn/article/gosphd.html


咨詢
建站咨詢
