JavaWeb--深入Servlet與JSP(運行原理)

復習復習?。?!搞起來?。ervlet和JSP是Java EE規(guī)范最基本成員,他們是Java Web開發(fā)的重點知識,即使我們經(jīng)常使用框架開發(fā)后端,但是我們還是很必要去理解他們的原理的。

文章結(jié)構(gòu):(1)剖析Servlet;(2)剖析JSP;


一、剖析Servlet:

(1)概述

Servlet是一種獨立于平臺和協(xié)議的服務(wù)器端的Java應(yīng)用程序,可以生成動態(tài)的web頁面。它擔當Web瀏覽器或其他http客戶程序發(fā)出請求、與http服務(wù)器上的數(shù)據(jù)庫或應(yīng)用程序之間交互的中間層。

Servlet是用Java編寫的Server端程序,它與協(xié)議和平臺無關(guān)。Servlet運行于Java服務(wù)器中。
Java Servlet可以動態(tài)地擴展服務(wù)器的能力,并采用請求-響應(yīng)模式提供Web服務(wù)。
Servlet是使用Java Servlet應(yīng)用程序設(shè)計接口及相關(guān)類和方法的Java程序。它在Web服務(wù)器上或應(yīng)用服務(wù)器上運行并擴展了該服務(wù)器的能力。Servlet裝入Web服務(wù)器并在Web服務(wù)器內(nèi)執(zhí)行。
Servlet是以Java技術(shù)為基礎(chǔ)的服務(wù)器端應(yīng)用程序組件,Servlet的客戶端可以提出請求并獲得該請求的響應(yīng),它可以是任何Java程序、瀏覽器或任何設(shè)備。

(2)基本知識:

1.配置

編輯好的servlet源文件并不能響應(yīng)用戶請求,還必須將其編譯成class文件,將編譯好的class文件放到WEB-INF/classes路徑下,如果servlet有包,則還需要將class文件放到包路徑下。

2.生命周期

這里寫圖片描述

編寫的JSP頁面最終將由web容器編譯成對應(yīng)的servlet,當servlet在容器中運行時,其實例的創(chuàng)建及銷毀等都不是有程序猿決定的,而是由web容器進行控制的。

servlet容器負責加載和實例化Servlet,在容器啟動時根據(jù)設(shè)置決定是在啟動時初始化(loadOnStartup大于等于0在容器啟動時進行初始化,值越小優(yōu)先級越高),還是延遲初始化直到第一次請求前;

初始化:

init(),執(zhí)行一些一次性的動作,可以通過ServletConfig配置對象,獲取初始化參數(shù),訪問ServletContext上下文環(huán)境;

請求處理:

servlet容器封裝Request和Response對象傳給對應(yīng)的servlet的service方法,對于HttpServlet,就是HttpServletRequest和HttpServletResponse; HttpServlet中使用模板方法模式,service方法根據(jù)HTTP請求方法進一步分派到doGet,doPost等不同的方法來進行處理;
對于HTTP請求的處理,只有重寫了支持HTTP方法的對應(yīng)HTTP servlet方法(doGet),才可以支持,否則放回405(Method Not Allowed)。

3.訪問servlet的配置參數(shù)

配置servlet時,還可以增加額外的配置參數(shù),通過使用配置參數(shù),可以實現(xiàn)提供更好的可移植性,避免將參數(shù)以編碼方式寫在程序代碼中。

配置參數(shù)有兩種方式:
(1)通過@WebServlet的initParams屬性來指定。
(2)通過在web.xml文件的<servlet.../>元素中添加<init-param.../>子元素來指定。

4.Servlet的數(shù)量

Servlet默認是線程不安全的,一個容器中只有每個servlet一個實例。

StandardWrapper源碼中寫明,這個類負責Servlet的創(chuàng)建,其中SingleThreadModule模式下創(chuàng)建的實例數(shù)不能超過20個,也就是同時只能支持20個線程訪問這個Serlvet,因此,這種對象池的設(shè)計會進一步限制并發(fā)能力和可伸縮性。

5.缺點

開發(fā)效率低、 程序可移植性差、 程序可維護性差

6.標準mvc模式中的servlet

僅作為控制器使用,JavaEE應(yīng)用架構(gòu)正是遵循mvc模式的,其中JSP僅作為表現(xiàn)層技術(shù),其作用有兩點:1.負責收集用戶請求參數(shù);2. 將應(yīng)用的處理結(jié)果、狀態(tài)、數(shù)據(jù)呈現(xiàn)給用戶。

7.**線程不安全 **:

servlet中默認線程不安全,單例多線程,因此對于共享的數(shù)據(jù)(靜態(tài)變量,堆中的對象實例等)自己維護進行同步控制,不要在service方法或doGet等由service分派出去的方法,直接使用synchronized方法,很顯然要根據(jù)業(yè)務(wù)控制同步控制塊的大小進行細粒度的控制,將不影響線程安全的耗時操作移出同步控制塊;

Servlet多線程機制背后有一個線程池在支持,線程池在初始化初期就創(chuàng)建了一定數(shù)量的線程對象,通過提高對這些對象的利用率,避免高頻率地創(chuàng)建對象,從而達到提高程序的效率的目的。(由線程來執(zhí)行Servlet的service方法,servlet在Tomcat中是以單例模式存在的, Servlet的線程安全問題只有在大量的并發(fā)訪問時才會顯現(xiàn)出來,并且很難發(fā)現(xiàn),因此在編寫Servlet程序時要特別注意。線程安全問題主要是由實例變量造成的,因此在Servlet中應(yīng)避免使用實例變量。如果應(yīng)用程設(shè)計無法避免使用實例變量,那么使用同步來保護要使用的實例變量,但為保證系統(tǒng)的最佳性能,應(yīng)該同步可用性最小的代碼路徑)

8.異步處理:

在Servlet中等待是一個低效的操作,因為這是阻塞操作。

異步處理請求能力,使線程可以返回到容器,從而執(zhí)行更多的任務(wù)。當開始異步處理請求時,另一個線程或回調(diào)可以:(1)產(chǎn)生響應(yīng);或者,(2)請求分派;或者,(3)調(diào)用完成;

關(guān)鍵方法:

啟用:讓servlet支持異步支持:asyncSupported=true;
啟動AsyncContextasyncContext=req.startAsyncContext();或startAsyncContext(req,resp);
完成:asyncContext.complete();必須在startAsync調(diào)用之后,分派進行之前調(diào)用;同一個AsyncContext不能同時調(diào)用dispatch和complete
分派:asyncContext.dispatch();dispatch(Stringpath);dispatch(ServletContextcontext,Stringpath); 不能在complete之后調(diào)用; 從一個同步servlet分派到異步servlet是非法的;
超時:
asyncContext.setTimeout(millis); 超時之后,將不能通過asyncContext進行操作,但是可以執(zhí)行其他耗時操作;
在異步周期開始后,容器啟動的分派已經(jīng)返回后,調(diào)用該方法拋出IllegalStateException;如果設(shè)置成0或小于0就表示notimeout; 超時表示HTTP連接已經(jīng)結(jié)束,HTTP已經(jīng)關(guān)閉,請求已經(jīng)結(jié)束了。

啟動新線程 :

通過AsyncCOntext.start(Runnable)方法,向線程池提交一個任務(wù),其中可以使用AsyncContext(未超時前);
事件監(jiān)聽:addListener(newAsyncListener{…});
onComplete:完成時回調(diào),如果進行了分派,onComplete方法將延遲到分派返回容器后進行調(diào)用;
onError:可以通過AsyncEvent.getThrowable獲取異常;
onTimeout:超時進行回調(diào);
onStartAsync:在該AsyncContext中啟動一個新的異步周期(調(diào)用startAsyncContext)時,進行回調(diào);

超時和異常處理,步驟:

(1)調(diào)用所有注冊的AsyncListener實例的onTimeout/onError;
(2)如果沒有任何AsyncListener調(diào)用AsyncContext.complete()或AsyncContext.dispatch(),執(zhí)行一個狀態(tài)碼為HttpServletResponse .SC_INTERNAL_SERVER_ERROR出錯分派;
(3)如果沒有找到錯誤頁面或者錯誤頁面沒有調(diào)用AsyncContext.complete()/dispatch(),容器要調(diào)用complete方法;

servlet生命終止:

servlet容器確定從服務(wù)中移除servlet時,可以通過調(diào)用destroy()方法將釋放servlet占用的任何資源和保存的持久化狀態(tài)等。調(diào)用destroy方法之前必須保證當前所有正在執(zhí)行service方法的線程執(zhí)行完成或者超時;
之后servlet實例可以被垃圾回收,當然什么時候回收并不確定,因此destroy方法是是否必要的。

(3)運行原理:

當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)負擔。

下圖粗暴解釋了請求到容器流程

這里寫圖片描述

下圖解釋了請求到容器到servlet周期流程

這里寫圖片描述

文字解說:

1.客戶發(fā)出請求—>Web 服務(wù)器轉(zhuǎn)發(fā)到Web容器Tomcat;

2.Tomcat主線程對轉(zhuǎn)發(fā)來用戶的請求做出響應(yīng)創(chuàng)建兩個對象:HttpServletRequest和HttpServletResponse;

3.從請求中的URL中找到正確Servlet,Tomcat為其創(chuàng)建或者分配一個線程,同時把步驟2創(chuàng)建的兩個對象傳遞給該線程;

4.Tomcat調(diào)用Servlet的servic()方法,根據(jù)請求參數(shù)的不同調(diào)用doGet()或者doPost()方法;

5.假設(shè)是HTTP GET請求,doGet()方法生成靜態(tài)頁面,并組合到響應(yīng)對象里;

Servlet線程結(jié)束時:Tomcat將響應(yīng)對象轉(zhuǎn)換為HTTP響應(yīng)發(fā)回給客戶,同時刪除請求和響應(yīng)對象。

可以理解Servlet的生命周期:Servlet類加載(對應(yīng)3步);Servlet實例化(對應(yīng)3步);調(diào)用init方法(對應(yīng)3步);調(diào)用service()方法(對應(yīng)4、5步);;調(diào)用destroy()方法(對應(yīng)6步)。

注意:

1.創(chuàng)建Servlet對象的時機:

Servlet容器啟動時:讀取web.xml配置文件中的信息,構(gòu)造指定的Servlet對象,創(chuàng)建ServletConfig對象,同時將ServletConfig對象作為參數(shù)來調(diào)用Servlet對象的init方法。

在Servlet容器啟動后:客戶首次向Servlet發(fā)出請求,Servlet容器會判斷內(nèi)存中是否存在指定的Servlet對象,如果沒有則創(chuàng)建它,然后根據(jù)客戶的請求創(chuàng)建HttpRequest、HttpResponse對象,從而調(diào)用Servlet 對象的service方法。

Servlet Servlet容器在啟動時自動創(chuàng)建Servlet,這是由在web.xml文件中為Servlet設(shè)置的<load-on-startup>屬性決定的。從中我們也能看到同一個類型的Servlet對象在Servlet容器中以單例的形式存在。

2.在Servlet接口和GenericServlet中是沒有doGet()、doPost()等等這些方法的,HttpServlet中定義了這些方法,但是都是返回error信息,所以,我們每次定義一個Servlet的時候,都必須實現(xiàn)doGet或doPost等這些方法。我們經(jīng)常使用的httpServlet是繼承于GenericServlet實現(xiàn)的。


二、剖析JSP

(1)概述:

JSP和Servlet的本質(zhì)是一樣的,因為JSP最終需要編譯成Servlet才能運行,換句話說JSP是生成Servler的草稿文件。

JSP就是在HTML中嵌入Java代碼,或者使用JSP標簽,包括使用用戶自定義標簽,從而可以動態(tài)的提供內(nèi)容。早起JSP應(yīng)用比較廣泛,一個web應(yīng)用可以全部由JSP頁面組成,只需要少量的JavaBean即可,但是這樣導致了JSP職責過于復雜,這是Java EE標準的出現(xiàn)無疑是雪中送炭,因此JSP慢慢發(fā)展成單一的表現(xiàn)技術(shù),不再承擔業(yè)務(wù)邏輯組件以及持久層組件的責任。

原理概述:(一會詳解)

JSP的本質(zhì)是servlet,當用戶指定servlet發(fā)送請求時,servlet利用輸出流動態(tài)生成HTML頁面。由于包含大量的HTML標簽。靜態(tài)文本等格式導致servlet的開發(fā)效率極低,所有的表現(xiàn)邏輯,包括布局、色彩及圖像等,都必須耦合在Java代碼中,起靜態(tài)的部分無需Java程序控制,只有那些需要從數(shù)據(jù)庫讀取或者需要動態(tài)生成的頁面內(nèi)容才使用Java腳本控制。
因此,JSP頁面內(nèi)容有以下兩部分組成:

靜態(tài)部分:HTML標簽

動態(tài)部分:Java腳本

(2)基本知識:

指令就省略了吧,隨便查都有一堆。

重點講講它的內(nèi)置對象

首先,我們可以自己去一個目錄去看看jsp編譯成servlet的代碼。目錄是:你的eclipse的工作目錄下:比如:E:\eclipse\workplace.metadata.plugins\org.eclipse.wst.server.core\tmp0\work\
從中,我們可以看到有九個隱藏對象,一些就final了,一些沒有。

1.request(使用最多):HttpServletRequest的一個對象(在JSP頁面可能會用到)。

Request范圍只針對服務(wù)器端跳轉(zhuǎn)。用于接收客戶端發(fā)送而來的請求信息。
注意:單一的參數(shù)可以使用getParameter()接收,而一組參數(shù)要用getParameterValues()接收。但要小心,如果getParameter和getParameterValues接收參數(shù)時,返回內(nèi)容是null,就可能產(chǎn)生NullPointerException,所以最好判斷接收來的參數(shù)是否為null。
獲取頭信息的名稱,可使用request的getHeaderNames()方法;而要想取出每個頭信息的內(nèi)容則需使用getHeader()方法。比如:語言、主機、Cookie等。

2.Response:

HttpServletResponse的一個對象(在JSP頁面中幾乎不會調(diào)用response的任何方法)

主要作用:對客戶端的請求進行回應(yīng),將Web服務(wù)器處理后的結(jié)果發(fā)回給客戶端。
設(shè)置頭信息:客戶端與服務(wù)器端經(jīng)常需要發(fā)送許多額外信息。服務(wù)器端可通過setHeader方法,將頭信息設(shè)置為refresh,并指定刷新時間,還有跳轉(zhuǎn)的路徑URL。如:例子就是那些頁面經(jīng)常提示的“3秒后跳轉(zhuǎn)到首頁”這樣的操作。
如果定時為0,則為無條件跳轉(zhuǎn)。注意:定時跳轉(zhuǎn)屬于客戶端跳轉(zhuǎn)。而且這種設(shè)置跳轉(zhuǎn)頭信息的方式,單純html也可以做,所以要結(jié)合實際考慮,如需請求的是動態(tài)頁則需JSP進行編寫

3.pageContext:

頁面的上下文,表示當前頁面,是一個PageContext的一個對象,可以從該對象中獲取到其他8個隱含對象,也可以從中獲取到當前頁面的其他信息。(學習自定義標簽時使用它,JSP頁面上很少直接使用,1`但很重要)。作用范圍僅在當前頁面。實際上pageContext可以設(shè)置任意范圍的屬性,而其他操作也是對這一功能的再度包裝而已。但一般習慣于使用pageContext對象設(shè)置保存在一頁范圍的屬性。很少使用他進行設(shè)置其他范圍的屬性。

4.session:

代表瀏覽器和服務(wù)器的一次會話,是HttpSession的一個對象,后面詳細學習。這個session屬性設(shè)置后,可在任何一個與設(shè)置頁面相關(guān)的頁面中獲取。也就是不管是客戶端跳轉(zhuǎn)還是服務(wù)器端跳轉(zhuǎn)都可以取得屬性。但是如果再打開一個新的瀏覽器訪問該jsp頁面,則無法取得session屬性。因為每個新的瀏覽器連接上服務(wù)器后就是一個新的session。

5.application:

代表當前web應(yīng)用。是ServletContext對象。這個設(shè)置的屬性可讓所有用戶(session)都看得見。這樣的屬性保存在服務(wù)器上。

6.config:

前JSP對應(yīng)的Servlet的ServletConfig對象(開發(fā)的時候幾乎不用)。若需要訪問當前JSP配置的初始化參數(shù),需要通過映射的地址才可以。
映射JSP方式:
這里寫圖片描述

7.out:

作用:完成頁面的輸出操作。但在開發(fā)中,一般是使用表達式完成輸出的。
JspWriter對象,經(jīng)常調(diào)用out.println() 可以直接把字符串打印到瀏覽器上。

8.page

指向當前JSP對應(yīng)的Servlet對象的引用,但為Object類型,只能調(diào)用Object類的方法(幾乎不使用)。就是當前JSP對象。

9.exception:

在聲明了page 指令的isErrorPage=”true”時,才可以使用。<%@ page isErrorPage="true"%>

大致使用頻率:

pageContext,request,session,application;(對屬性的作用域的范圍從小到大)

out,response,config,page,exception

(3)JSP運行原理:

這里寫圖片描述

這里寫圖片描述

1.WEB容器(Servlet引擎)接收到以.jsp為擴展名的URL的訪問請求時,容器會把訪問請求交給JSP引擎去處理

2.每個JSP頁面在第一次被訪問時,JSP引擎將它翻譯成一個Servlet源程序,接著再把這個Servlet源程序編譯成Servlet的.class類文件,然后再由WEB容器(Servlet引擎)像調(diào)用普通Servlet程序一樣的方式來裝載和解釋執(zhí)行這個由JSP頁面翻譯成的Servlet程序,并執(zhí)行該servlet實例的jspInit()方法(jspInit()方法在Servlet的生命周期中只被執(zhí)行一次)。。

3.然后創(chuàng)建并啟動一個新的線程,新線程調(diào)用實例的jspService()方法。(對于每一個請求,JSP引擎會創(chuàng)建一個新的線程來處理該請求。如果有多個客戶端同時請求該JSP文件,則JSP引擎會創(chuàng)建多個線程,每個客戶端請求對應(yīng)一個線程)。

4.瀏覽器在調(diào)用JSP文件時,Servlet容器會把瀏覽器的請求和對瀏覽器的回應(yīng)封裝成HttpServletRequest和HttpServletResponse對象,同時調(diào)用對應(yīng)的Servlet實例中的jspService()方法,把這兩個對象作為參數(shù)傳遞到j(luò)spService()方法中。

5.jspService()方法執(zhí)行后會將HTML內(nèi)容返回給客戶端。

如果JSP文件被修改了,服務(wù)器將根據(jù)設(shè)置決定是否對該文件進行重新編譯。如果需要重新編譯,則將編譯結(jié)果取代內(nèi)存中的Servlet,并繼續(xù)上述處理過程。 如果在任何時候由于系統(tǒng)資源不足,JSP引擎將以某種不確定的方式將Servlet從內(nèi)存中移去。當這種情況發(fā)生時,jspDestroy()方法首先被調(diào)用, 然后Servlet實例便被標記加入“垃圾收集”處理。

補充:

1.JSP規(guī)范也沒有明確要求JSP中的腳本程序代碼必須采用Java語言,JSP中的腳本程序代碼可以采用Java語言之外的其他腳本語言來編寫,但是JSP頁面最終必須轉(zhuǎn)換成JavaServlet程序。

2.可以在WEB應(yīng)用程序正式發(fā)布之前,將其中的所有JSP頁面預(yù)先編譯成Servlet程序。

3.以多線程方式執(zhí)行可大大降低對系統(tǒng)的資源需求,提高系統(tǒng)的并發(fā)量及響應(yīng)時間,但應(yīng)該注意多線程的編程限制,由于該Servlet始終駐于內(nèi)存,所以響應(yīng)是非??斓?。

4.雖然JSP效率很高,但在第一次調(diào)用時由于需要轉(zhuǎn)換和編譯而有一些輕微的延遲。在jspInit()中可以進行一些初始化工作,如建立與數(shù)據(jù)庫的連接、建立網(wǎng)絡(luò)連接、從配置文件中獲取一些參數(shù)等,而在jspDestory()中釋放相應(yīng)的資源。


參考博客:

http://blog.csdn.net/fengdongkun/article/details/8159381

http://www.cnblogs.com/mlloc-clove/p/3549777.html


好了,JavaWeb--深入Servlet與JSP(運行原理)講完了。本博客是我復習階段的一些筆記,拿來分享經(jīng)驗給大家。歡迎在下面指出錯誤,共同學習??!你的點贊是對我最好的支持!!

更多內(nèi)容,可以訪問JackFrost的博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 從三月份找實習到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時芥藍閱讀 42,755評論 11 349
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,623評論 18 399
  • 0 系列目錄# WEB請求處理 WEB請求處理一:瀏覽器請求發(fā)起處理 WEB請求處理二:Nginx請求反向代理 本...
    七寸知架構(gòu)閱讀 14,233評論 22 189
  • 這部分主要是與Java Web和Web Service相關(guān)的面試題。 96、闡述Servlet和CGI的區(qū)別? 答...
    雜貨鋪老板閱讀 1,500評論 0 10
  • 題目1: DOM0 事件和DOM2級在事件監(jiān)聽使用方式上有什么區(qū)別? DOM0級事件,添加多個事件處理程序時, 會...
    進擊的前端_風笑影閱讀 135評論 0 0

友情鏈接更多精彩內(nèi)容