1. 程式人生 > >JavaWeb--深入Servlet與JSP(執行原理)

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

複習複習!!!搞起來!!Servlet和JSP是Java EE規範最基本成員,他們是Java Web開發的重點知識,即使我們經常使用框架開發後端,但是我們還是很必要去理解他們的原理的。

文章結構:(1)剖析Servlet;(2)剖析JSP;

一、剖析Servlet:

(1)概述

Servlet是一種獨立於平臺和協議的伺服器端的Java應用程式,可以生成動態的web頁面。它擔當Web瀏覽器或其他http客戶程式發出請求、與http伺服器上的資料庫或應用程式之間互動的中間層。

Servlet是用Java編寫的Server端程式,它與協議和平臺無關。Servlet運行於Java伺服器中。
Java Servlet可以動態地擴充套件伺服器的能力,並採用請求-響應模式提供Web服務。
Servlet是使用Java Servlet應用程式設計介面及相關類和方法的Java程式。它在Web伺服器上或應用伺服器上執行並擴充套件了該伺服器的能力。Servlet裝入Web伺服器並在Web伺服器內執行。
Servlet是以Java技術為基礎的伺服器端應用程式元件,Servlet的客戶端可以提出請求並獲得該請求的響應,它可以是任何Java程式、瀏覽器或任何裝置。

(2)基本知識:

1.配置

編輯好的servlet原始檔並不能響應使用者請求,還必須將其編譯成class檔案,將編譯好的class檔案放到WEB-INF/classes路徑下,如果servlet有包,則還需要將class檔案放到包路徑下。

2.生命週期

這裡寫圖片描述

編寫的JSP頁面最終將由web容器編譯成對應的servlet,當servlet在容器中執行時,其例項的建立及銷燬等都不是有程式猿決定的,而是由web容器進行控制的。

servlet容器負責載入和例項化Servlet,在容器啟動時根據設定決定是在啟動時初始化(loadOnStartup大於等於0在容器啟動時進行初始化,值越小優先順序越高),還是延遲初始化直到第一次請求前;

初始化:

init(),執行一些一次性的動作,可以通過ServletConfig配置物件,獲取初始化引數,訪問ServletContext上下文環境;

請求處理:

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

3.訪問servlet的配置引數

配置servlet時,還可以增加額外的配置引數,通過使用配置引數,可以實現提供更好的可移植性,避免將引數以編碼方式寫在程式程式碼中。

配置引數有兩種方式:
(1)通過@WebServlet的initParams屬性來指定。
(2)通過在web.xml檔案的

4.Servlet的數量

Servlet預設是執行緒不安全的,一個容器中只有每個servlet一個例項。

StandardWrapper原始碼中寫明,這個類負責Servlet的建立,其中SingleThreadModule模式下建立的例項數不能超過20個,也就是同時只能支援20個執行緒訪問這個Serlvet,因此,這種物件池的設計會進一步限制併發能力和可伸縮性。

5.缺點

開發效率低、 程式可移植性差、 程式可維護性差

6.標準mvc模式中的servlet

僅作為控制器使用,JavaEE應用架構正是遵循mvc模式的,其中JSP僅作為表現層技術,其作用有兩點:1.負責收集使用者請求引數;2. 將應用的處理結果、狀態、資料呈現給使用者。

7.執行緒不安全

servlet中預設執行緒不安全,單例多執行緒,因此對於共享的資料(靜態變數,堆中的物件例項等)自己維護進行同步控制,不要在service方法或doGet等由service分派出去的方法,直接使用synchronized方法,很顯然要根據業務控制同步控制塊的大小進行細粒度的控制,將不影響執行緒安全的耗時操作移出同步控制塊;

Servlet多執行緒機制背後有一個執行緒池在支援,執行緒池在初始化初期就建立了一定數量的執行緒物件,通過提高對這些物件的利用率,避免高頻率地建立物件,從而達到提高程式的效率的目的。(由執行緒來執行Servlet的service方法,servlet在Tomcat中是以單例模式存在的, Servlet的執行緒安全問題只有在大量的併發訪問時才會顯現出來,並且很難發現,因此在編寫Servlet程式時要特別注意。執行緒安全問題主要是由例項變數造成的,因此在Servlet中應避免使用例項變數。如果應用程設計無法避免使用例項變數,那麼使用同步來保護要使用的例項變數,但為保證系統的最佳效能,應該同步可用性最小的程式碼路徑)

8.非同步處理:

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

非同步處理請求能力,使執行緒可以返回到容器,從而執行更多的任務。當開始非同步處理請求時,另一個執行緒或回撥可以:(1)產生響應;或者,(2)請求分派;或者,(3)呼叫完成;

關鍵方法:

啟用:讓servlet支援非同步支援:asyncSupported=true;
啟動AsyncContextasyncContext=req.startAsyncContext();或startAsyncContext(req,resp);
完成:asyncContext.complete();必須在startAsync呼叫之後,分派進行之前呼叫;同一個AsyncContext不能同時呼叫dispatch和complete
分派:asyncContext.dispatch();dispatch(Stringpath);dispatch(ServletContextcontext,Stringpath); 不能在complete之後呼叫; 從一個同步servlet分派到非同步servlet是非法的;
超時:
asyncContext.setTimeout(millis); 超時之後,將不能通過asyncContext進行操作,但是可以執行其他耗時操作;
在非同步週期開始後,容器啟動的分派已經返回後,呼叫該方法丟擲IllegalStateException;如果設定成0或小於0就表示notimeout; 超時表示HTTP連線已經結束,HTTP已經關閉,請求已經結束了。

啟動新執行緒 :

通過AsyncCOntext.start(Runnable)方法,向執行緒池提交一個任務,其中可以使用AsyncContext(未超時前);
事件監聽:addListener(newAsyncListener{…});
onComplete:完成時回撥,如果進行了分派,onComplete方法將延遲到分派返回容器後進行呼叫;
onError:可以通過AsyncEvent.getThrowable獲取異常;
onTimeout:超時進行回撥;
onStartAsync:在該AsyncContext中啟動一個新的非同步週期(呼叫startAsyncContext)時,進行回撥;

超時和異常處理,步驟:

(1)呼叫所有註冊的AsyncListener例項的onTimeout/onError;
(2)如果沒有任何AsyncListener呼叫AsyncContext.complete()或AsyncContext.dispatch(),執行一個狀態碼為HttpServletResponse .SC_INTERNAL_SERVER_ERROR出錯分派;
(3)如果沒有找到錯誤頁面或者錯誤頁面沒有呼叫AsyncContext.complete()/dispatch(),容器要呼叫complete方法;

servlet生命終止:

servlet容器確定從服務中移除servlet時,可以通過呼叫destroy()方法將釋放servlet佔用的任何資源和儲存的持久化狀態等。呼叫destroy方法之前必須保證當前所有正在執行service方法的執行緒執行完成或者超時;
之後servlet例項可以被垃圾回收,當然什麼時候回收並不確定,因此destroy方法是是否必要的。

(3)執行原理:

當Web伺服器接收到一個HTTP請求時,它會先判斷請求內容——如果是靜態網頁資料,Web伺服器將會自行處理,然後產生響應資訊;如果牽涉到動態資料,Web伺服器會將請求轉交給Servlet容器。此時Servlet容器會找到對應的處理該請求的Servlet例項來處理,結果會送回Web伺服器,再由Web伺服器傳回使用者端。

針對同一個Servlet,Servlet容器會在第一次收到http請求時建立一個Servlet例項,然後啟動一個執行緒。第二次收到http請求時,Servlet容器無須建立相同的Servlet例項,而是啟動第二個執行緒來服務客戶端請求。所以多執行緒方式不但可以提高Web應用程式的執行效率,也可以降低Web伺服器的系統負擔。

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

這裡寫圖片描述

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

這裡寫圖片描述

文字解說:

1.客戶發出請求—>Web 伺服器轉發到Web容器Tomcat;

2.Tomcat主執行緒對轉發來使用者的請求做出響應建立兩個物件:HttpServletRequest和HttpServletResponse;

3.從請求中的URL中找到正確Servlet,Tomcat為其建立或者分配一個執行緒,同時把步驟2建立的兩個物件傳遞給該執行緒;

4.Tomcat呼叫Servlet的servic()方法,根據請求引數的不同調用doGet()或者doPost()方法;

5.假設是HTTP GET請求,doGet()方法生成靜態頁面,並組合到響應物件裡;

Servlet執行緒結束時:Tomcat將響應物件轉換為HTTP響應發回給客戶,同時刪除請求和響應物件。

可以理解Servlet的生命週期:Servlet類載入(對應3步);Servlet例項化(對應3步);呼叫init方法(對應3步);呼叫service()方法(對應4、5步);;呼叫destroy()方法(對應6步)。

注意:

1.建立Servlet物件的時機:

Servlet容器啟動時:讀取web.xml配置檔案中的資訊,構造指定的Servlet物件,建立ServletConfig物件,同時將ServletConfig物件作為引數來呼叫Servlet物件的init方法。

在Servlet容器啟動後:客戶首次向Servlet發出請求,Servlet容器會判斷記憶體中是否存在指定的Servlet物件,如果沒有則建立它,然後根據客戶的請求建立HttpRequest、HttpResponse物件,從而呼叫Servlet 物件的service方法。

Servlet Servlet容器在啟動時自動建立Servlet,這是由在web.xml檔案中為Servlet設定的屬性決定的。從中我們也能看到同一個型別的Servlet物件在Servlet容器中以單例的形式存在。

2.在Servlet介面和GenericServlet中是沒有doGet()、doPost()等等這些方法的,HttpServlet中定義了這些方法,但是都是返回error資訊,所以,我們每次定義一個Servlet的時候,都必須實現doGet或doPost等這些方法。我們經常使用的httpServlet是繼承於GenericServlet實現的。

二、剖析JSP

(1)概述:

JSP和Servlet的本質是一樣的,因為JSP最終需要編譯成Servlet才能執行,換句話說JSP是生成Servler的草稿檔案。

JSP就是在HTML中嵌入Java程式碼,或者使用JSP標籤,包括使用使用者自定義標籤,從而可以動態的提供內容。早起JSP應用比較廣泛,一個web應用可以全部由JSP頁面組成,只需要少量的JavaBean即可,但是這樣導致了JSP職責過於複雜,這是Java EE標準的出現無疑是雪中送炭,因此JSP慢慢發展成單一的表現技術,不再承擔業務邏輯元件以及持久層元件的責任。

原理概述:(一會詳解)

JSP的本質是servlet,當用戶指定servlet傳送請求時,servlet利用輸出流動態生成HTML頁面。由於包含大量的HTML標籤。靜態文字等格式導致servlet的開發效率極低,所有的表現邏輯,包括佈局、色彩及影象等,都必須耦合在Java程式碼中,起靜態的部分無需Java程式控制,只有那些需要從資料庫讀取或者需要動態生成的頁面內容才使用Java指令碼控制。
因此,JSP頁面內容有以下兩部分組成:

靜態部分:HTML標籤

動態部分:Java指令碼

(2)基本知識:

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

重點講講它的內建物件

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

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

Request範圍只針對伺服器端跳轉。用於接收客戶端傳送而來的請求資訊。
注意:單一的引數可以使用getParameter()接收,而一組引數要用getParameterValues()接收。但要小心,如果getParameter和getParameterValues接收引數時,返回內容是null,就可能產生NullPointerException,所以最好判斷接收來的引數是否為null。
獲取頭資訊的名稱,可使用request的getHeaderNames()方法;而要想取出每個頭資訊的內容則需使用getHeader()方法。比如:語言、主機、Cookie等。

2.Response:

HttpServletResponse的一個物件(在JSP頁面中幾乎不會呼叫response的任何方法)

主要作用:對客戶端的請求進行迴應,將Web伺服器處理後的結果發回給客戶端。
設定頭資訊:客戶端與伺服器端經常需要傳送許多額外資訊。伺服器端可通過setHeader方法,將頭資訊設定為refresh,並指定重新整理時間,還有跳轉的路徑URL。如:例子就是那些頁面經常提示的“3秒後跳轉到首頁”這樣的操作。
如果定時為0,則為無條件跳轉。注意:定時跳轉屬於客戶端跳轉。而且這種設定跳轉頭資訊的方式,單純html也可以做,所以要結合實際考慮,如需請求的是動態頁則需JSP進行編寫

3.pageContext:

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

4.session:

代表瀏覽器和伺服器的一次會話,是HttpSession的一個物件,後面詳細學習。這個session屬性設定後,可在任何一個與設定頁面相關的頁面中獲取。也就是不管是客戶端跳轉還是伺服器端跳轉都可以取得屬性。但是如果再開啟一個新的瀏覽器訪問該jsp頁面,則無法取得session屬性。因為每個新的瀏覽器連線上伺服器後就是一個新的session。

5.application:

代表當前web應用。是ServletContext物件。這個設定的屬性可讓所有使用者(session)都看得見。這樣的屬性儲存在伺服器上。

6.config:

前JSP對應的Servlet的ServletConfig物件(開發的時候幾乎不用)。若需要訪問當前JSP配置的初始化引數,需要通過對映的地址才可以。
對映JSP方式:

這裡寫圖片描述

7.out:

作用:完成頁面的輸出操作。但在開發中,一般是使用表示式完成輸出的。
JspWriter物件,經常呼叫out.println() 可以直接把字串列印到瀏覽器上。

8.page

指向當前JSP對應的Servlet物件的引用,但為Object型別,只能呼叫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引擎)像呼叫普通Servlet程式一樣的方式來裝載和解釋執行這個由JSP頁面翻譯成的Servlet程式,並執行該servlet例項的jspInit()方法(jspInit()方法在Servlet的生命週期中只被執行一次)。。

3.然後建立並啟動一個新的執行緒,新執行緒呼叫例項的jspService()方法。(對於每一個請求,JSP引擎會建立一個新的執行緒來處理該請求。如果有多個客戶端同時請求該JSP檔案,則JSP引擎會建立多個執行緒,每個客戶端請求對應一個執行緒)。

4.瀏覽器在呼叫JSP檔案時,Servlet容器會把瀏覽器的請求和對瀏覽器的迴應封裝成HttpServletRequest和HttpServletResponse物件,同時呼叫對應的Servlet例項中的jspService()方法,把這兩個物件作為引數傳遞到jspService()方法中。

5.jspService()方法執行後會將HTML內容返回給客戶端。

如果JSP檔案被修改了,伺服器將根據設定決定是否對該檔案進行重新編譯。如果需要重新編譯,則將編譯結果取代記憶體中的Servlet,並繼續上述處理過程。 如果在任何時候由於系統資源不足,JSP引擎將以某種不確定的方式將Servlet從記憶體中移去。當這種情況發生時,jspDestroy()方法首先被呼叫, 然後Servlet例項便被標記加入“垃圾收集”處理。

補充:

1.JSP規範也沒有明確要求JSP中的指令碼程式程式碼必須採用Java語言,JSP中的指令碼程式程式碼可以採用Java語言之外的其他指令碼語言來編寫,但是JSP頁面最終必須轉換成JavaServlet程式。

2.可以在WEB應用程式正式釋出之前,將其中的所有JSP頁面預先編譯成Servlet程式。

3.以多執行緒方式執行可大大降低對系統的資源需求,提高系統的併發量及響應時間,但應該注意多執行緒的程式設計限制,由於該Servlet始終駐於記憶體,所以響應是非常快的。

4.雖然JSP效率很高,但在第一次呼叫時由於需要轉換和編譯而有一些輕微的延遲。在jspInit()中可以進行一些初始化工作,如建立與資料庫的連線、建立網路連線、從配置檔案中獲取一些引數等,而在jspDestory()中釋放相應的資源。

參考部落格:

好了,JavaWeb–深入Servlet與JSP(執行原理)講完了。本部落格是我複習階段的一些筆記,拿來分享經驗給大家。歡迎在下面指出錯誤,共同學習!!你的點贊是對我最好的支援!!