servlet的執行原理與生命週期
一、先從servlet容器說起:大家最為熟悉的servlet容器就是Tomcat ,Servlet 容器是如何管理 Servlet?
先看一下tomcat的容器模型:
從上圖可以看出 Tomcat 的容器分為四個等級,真正管理Servlet 的容器是Context 容器,一個 Context 對應一個 Web 工程
從上圖可以看出 Tomcat 的容器分為四個等級,真正管理Servlet 的容器是Context 容器,一個 Context 對應一個 Web 工程
Tomcat 的容器等級中,Context 容器是直接管理 Servlet 在容器中的包裝類Wrapper(StandardWrapper)的容器,所以 Context 容器如何執行將直接影響 Servlet 的工作方式。
這裡解釋一下servlet的包裝類:StandardWrapper,這裡有個疑問,為什麼要將 Servlet 包裝成 StandardWrapper 而不直接是 Servlet 物件。因為StandardWrapper 是 Tomcat 容器中的一部分,它具有容器的特徵,而 Servlet 為一個獨立的 web 開發標準,不應該強耦合在 Tomcat 中。
除了將 Servlet 包裝成 StandardWrapper 並作為子容器新增到 Context 中,其它的所有 web.xml 屬性都被解析到 Context 中,所以說 Context 容器才是真正執行 Servlet 的 Servlet 容器。一個 Web 應用對應一個 Context 容器,容器的配置屬性由應用的 web.xml 指定,這樣我們就能理解 web.xml 到底起到什麼作用了
二、下面簡述一下servlet的工作工程:
Web伺服器在與客戶端互動時.Servlet的工作過程是:
1. 在客戶端對web伺服器發出請求
2. web伺服器接收到請求後將其傳送給Servlet
3. Servlet容器為此產生一個例項物件並呼叫ServletAPI中相應的方法來對客戶端HTTP請求進行處理,然後將處理的響應結果返回給WEB伺服器.
4. web伺服器將從Servlet例項物件中收到的響應結構傳送回客戶端.
三、servlet的生命週期:
如上圖所示,Servlet的生命週期可以分為四個階段,即裝載類及建立例項階段、初始化階段、服務階段和例項銷燬階段。下面針對每個階段的程式設計任務及注意事項進行詳細的說明。
1、建立servlet例項:
在預設情況下Servlet例項是在第一個請求到來的時候建立,以後複用。如果有的Servlet需要複雜的操作需要載初始化時完成,比如開啟檔案、初始化網路連線等,可以通知伺服器在啟動的時候建立該Servlet的例項。具體配置如下:
<servlet>
<servlet-name>TimeServlet</servlet-name>
<servlet-class>com.allanlxf.servlet.basic.TimeServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
建立servlet物件的相關類結構:
2、初始化
一旦Servlet例項被建立,Web伺服器會自動呼叫init(ServletConfig config)方法來初始化該Servlet。其中方法引數config中包含了Servlet的配置資訊,比如初始化引數,該物件由伺服器建立。
I.如何配置Servlet的初始化引數?
在web.xml中該Servlet的定義標記中,比如:
<servlet>
<servlet-name>TimeServlet</servlet-name>
<servlet-class>com.allanlxf.servlet.basic.TimeServlet</servlet-class>
<init-param>
<param-name>user</param-name>
<param-value>username</param-value>
</init-param>
<init-param>
<param-name>blog</param-name>
<param-value>http://。。。</param-value>
</init-param>
</servlet>
配置了兩個初始化引數user和blog它們的值分別為username和http://。。。, 這樣以後要修改使用者名稱和部落格的地址不需要修改Servlet程式碼,只需修改配置檔案即可。
II.如何讀取Servlet的初始化引數?
ServletConfig中定義瞭如下的方法用來讀取初始化引數的資訊:
public String getInitParameter(String name)
引數:初始化引數的名稱。
返回:初始化引數的值,如果沒有配置,返回null。
III.init(ServletConfig)方法執行次數
在Servlet的生命週期中,該方法執行一次。
IV.init(ServletConfig)方法與執行緒
該方法執行在單執行緒的環境下,因此開發者不用考慮執行緒安全的問題。
V.init(ServletConfig)方法與異常
該方法在執行過程中可以丟擲ServletException來通知Web伺服器Servlet例項初始化失敗。一旦ServletException丟擲,Web伺服器不會將客戶端請求交給該Servlet例項來處理,而是報告初始化失敗異常資訊給客戶端,該Servlet例項將被從記憶體中銷燬。如果在來新的請求,Web伺服器會建立新的Servlet例項,並執行新例項的初始化操作
3、服務
一旦Servlet例項成功建立及初始化,該Servlet例項就可以被伺服器用來服務於客戶端的請求並生成響應。在服務階段Web伺服器會呼叫該例項的service(ServletRequest request, ServletResponse response)方法,request物件和response物件有伺服器建立並傳給Servlet例項。request物件封裝了客戶端發往伺服器端的資訊,response物件封裝了伺服器發往客戶端的資訊。
I. service()方法的職責
service()方法為Servlet的核心方法,客戶端的業務邏輯應該在該方法內執行,典型的服務方法的開發流程為:
解析客戶端請求-〉執行業務邏輯-〉輸出響應頁面到客戶端
II.service()方法與執行緒
為了提高效率,Servlet規範要求一個Servlet例項必須能夠同時服務於多個客戶端請求,即service()方法執行在多執行緒的環境下,Servlet開發者必須保證該方法的執行緒安全性。
III.service()方法與異常
service()方法在執行的過程中可以丟擲ServletException和IOException。其中ServletException可以在處理客戶端請求的過程中丟擲,比如請求的資源不可用、資料庫不可用等。一旦該異常丟擲,容器必須回收請求物件,並報告客戶端該異常資訊。IOException表示輸入輸出的錯誤,程式設計者不必關心該異常,直接由容器報告給客戶端即可。
程式設計注意事項說明:
1) 當Server Thread執行緒執行Servlet例項的init()方法時,所有的Client Service Thread執行緒都不能執行該例項的service()方法,更沒有執行緒能夠執行該例項的destroy()方法,因此Servlet的init()方法是工作在單執行緒的環境下,開發者不必考慮任何執行緒安全的問題。
2) 當伺服器接收到來自客戶端的多個請求時,伺服器會在單獨的Client Service Thread執行緒中執行Servlet例項的service()方法服務於每個客戶端。此時會有多個執行緒同時執行同一個Servlet例項的service()方法,因此必須考慮執行緒安全的問題。
3) 請大家注意,雖然service()方法執行在多執行緒的環境下,並不一定要同步該方法。而是要看這個方法在執行過程中訪問的資源型別及對資源的訪問方式。分析如下:
i. 如果service()方法沒有訪問Servlet的成員變數也沒有訪問全域性的資源比如靜態變數、檔案、資料庫連線等,而是隻使用了當前執行緒自己的資源,比如非指向全域性資源的臨時變數、request和response物件等。該方法本身就是執行緒安全的,不必進行任何的同步控制。
ii. 如果service()方法訪問了Servlet的成員變數,但是對該變數的操作是隻讀操作,該方法本身就是執行緒安全的,不必進行任何的同步控制。
iii. 如果service()方法訪問了Servlet的成員變數,並且對該變數的操作既有讀又有寫,通常需要加上同步控制語句。
iv. 如果service()方法訪問了全域性的靜態變數,如果同一時刻系統中也可能有其它執行緒訪問該靜態變數,如果既有讀也有寫的操作,通常需要加上同步控制語句。
v. 如果service()方法訪問了全域性的資源,比如檔案、資料庫連線等,通常需要加上同步控制語句。
4、銷燬
當Web伺服器認為Servlet例項沒有存在的必要了,比如應用重新裝載,或伺服器關閉,以及Servlet很長時間都沒有被訪問過。伺服器可以從記憶體中銷燬(也叫解除安裝)該例項。Web伺服器必須保證在解除安裝Servlet例項之前呼叫該例項的destroy()方法,以便回收Servlet申請的資源或進行其它的重要的處理。
Web伺服器必須保證呼叫destroy()方法之前,讓所有正在執行在該例項的service()方法中的執行緒退出或者等待這些執行緒一段時間。一旦destroy()方法已經執行,Web伺服器將拒絕所有的新到來的對該Servlet例項的請求,destroy()方法退出,該Servlet例項即可以被垃圾回收。
四、servlet解析客戶端http請求流程圖:
1. web客戶向Servlet容器發出HTTP請求;
2. Servlet容器解析web的HTTP請求.
3. Servlet容器建立一個HttpRequest物件,在這個物件中封裝了http請求資訊;
4. Servlet容器建立一個HttpResponse物件;
5. Servlet容器(如果訪問的該servlet不是在伺服器啟動時建立的,則先建立servlet例項並呼叫init()方法初始化物件)呼叫HttpServlet的service()方法,把HttpRequest和HttpResponse物件為service方法的引數傳給HttpServlet物件;
6. HttpServlet呼叫HttpRequest的有關方法,獲取HTTP請求資訊;
7. HttpServlet呼叫HttpResponse的有關方法,生成響應資料;
8. Servlet容器把HttpServlet的響應結果傳給web客戶.