JavaWeb--深入Servlet與JSP(執行原理)
阿新 • • 發佈:2019-01-24
複習複習!!!搞起來!!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執行原理: