JavaWeb--深刻Servlet與JSP(運行原理)
複習複習!!!搞起來!!Servlet和JSP是Java EE規範最基本成員,他們是Java Web開發的重點知識,即便咱們常用框架開發後端,可是咱們仍是很必要去理解他們的原理的。
文章結構:(1)剖析Servlet;(2)剖析JSP;
1、剖析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()方法(對應四、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實現的。
2、剖析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)基本知識:
指令就省略了吧,隨便查都有一堆。
重點講講它的內置對象:
從中,咱們能夠看到有九個隱藏對象,一些就final了,一些沒有。
1.request(使用最多):HttpServletRequest的一個對象(在JSP頁面可能會用到)。
Request範圍只針對服務器端跳轉。用於接收客戶端發送而來的請求信息。
注意:單一的參數可使用getParameter()接收,而一組參數要用getParameterValues()接收。但要當心,若是getParameter和getParameterValues接收參數時,返回內容是null,就可能產生NullPointerException,因此最好判斷接收來的參數是否爲null。
2.Response:
HttpServletResponse的一個對象(在JSP頁面中幾乎不會調用response的任何方法)
主要做用:對客戶端的請求進行迴應,將Web服務器處理後的結果發回給客戶端。
若是定時爲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(運行原理)講完了。本博客是我複習階段的一些筆記,拿來分享經驗給你們。歡迎在下面指出錯誤,共同窗習!!你的點贊是對我最好的支持!!