Servlet運行在Servlet容器中,其生命週期由容器來管理。Servlet的生命週期通過javax.servlet.Servlet接口中的init()、service()和destroy()方法來表示
Servlet的生命週期包含了下面4個階段:
1.加載和實例化
2.初始化
3.請求處理
4.服務終止
Web服務器在與客戶端交互時Servlet的工作過程是:
1. 在客戶端對web服務器發出請求
2. web服務器接收到請求後將其發送給Servlet
3. Servlet容器爲此產生一個實例對象並調用ServletAPI中相應的方法來對客戶端HTTP請求進行處理,然後將處理的響應結果返回給WEB服務器.
4. web服務器將從Servlet實例對象中收到的響應結構發送回客戶端.
servlet的生命週期:
1.加載和實例化
Servlet容器負責加載和實例化Servlet。當Servlet容器啓動時,或者在容器檢測到需要這個Servlet來響應第一個請求時,創建Servlet實例。當Servlet容器
啓動後,它必須要知道所需的Servlet類在什麼位置,Servlet容器可以從本地文件系統、遠程文件系統或者其他的網絡服務中通過類加載器加載Servlet類,
成功加載後,容器創建Servlet的實例。因爲容器是通過Java的反射API來創建Servlet實例,調用的是Servlet的默認構造方法(即不帶參數的構造方法),所
以我們在編寫Servlet類的時候,不應該提供帶參數的構造方法。
2.初始化
在Servlet實例化之後,容器將調用Servlet的init()方法初始化這個對象。初始化的目的是爲了讓Servlet對象在處理客戶端請求前完成一些初始化的工作,
如建立數據庫的連接,獲取配置信息等。對於每一個Servlet實例,init()方法只被調用一次。在初始化期間,Servlet實例可以使用容器爲它準備的
ServletConfig對象從Web應用程序的配置信息(在web.xml中配置)中獲取初始化的參數信息。在初始化期間,如果發生錯誤,Servlet實例可以拋出
ServletException異常或者UnavailableException異常來通知容器。ServletException異常用於指明一般的初始化失敗,例如沒有找到初始化參數;而
UnavailableException異常用於通知容器該Servlet實例不可用。例如,數據庫服務器沒有啓動,數據庫連接無法建立,Servlet就可以拋出
UnavailableException異常向容器指出它暫時或永久不可用。
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的service()方法對請求進行處理。要注意的是,在service()方法調用之前,init()方法必須成功執行。在service()方法中,
Servlet實例通過ServletRequest對象得到客戶端的相關信息和請求信息,在對請求進行處理後,調用ServletResponse對象的方法設置響應信息。在service
()方法執行期間,如果發生錯誤,Servlet實例可以拋出ServletException異常或者UnavailableException異常。如果UnavailableException異常指示了該實
例永久不可用,Servlet容器將調用實例的destroy()方法,釋放該實例。此後對該實例的任何請求,都將收到容器發送的HTTP 404(請求的資源不可用)響應
。如果UnavailableException異常指示了該實例暫時不可用,那麼在暫時不可用的時間段內,對該實例的任何請求,都將收到容器發送的HTTP 503(服務器暫
時忙,不能處理請求)響應。
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.服務終止
當容器檢測到一個Servlet實例應該從服務中被移除的時候,容器就會調用實例的destroy()方法,以便讓該實例可以釋放它所使用的資源,保存數據到持久存
儲設備中。當需要釋放內存或者容器關閉時,容器就會調用Servlet實例的destroy()方法。在destroy()方法調用之後,容器會釋放這個Servlet實例,該實例
隨後會被Java的垃圾收集器所回收。如果再次需要這個Servlet處理請求,Servlet容器會創建一個新的Servlet實例。
在整個Servlet的生命週期過程中,創建Servlet實例、調用實例的init()和destroy()方法都只進行一次,當初始化完成後,Servlet容器會將該實例保存在內存中,通過調用它的service()方法,爲接收到的請求服務。
5.異步Servlet
再Servlet3.0中開始支持異步處理方式。
Servlet 3.0 之前,一個普通 Servlet 的主要工作流程大致如下:
(1)首先,Servlet 接收到請求之後,可能需要對請求攜帶的數據進行一些預處理;
(2)接着,調用業務接口的某些方法,以完成業務處理;
(3)最後,根據處理的結果提交響應,Servlet 線程結束。
其中第二步的業務處理通常是最耗時的,這主要體現在數據庫操作,以及其它的跨網絡調用等,在此過程中,Servlet 線程一直處於阻塞狀態,直到業務方法執行完畢。
在處理業務的過程中,Servlet 資源一直被佔用而得不到釋放,對於併發較大的應用,這有可能造成性能的瓶頸。對此,在以前通常是採用私有解決方案來提前結束 Servlet 線程,並及時釋放資源。
Servlet 3.0 針對這個問題做了開創性的工作,現在通過使用 Servlet 3.0 的異步處理支持,之前的 Servlet 處理流程可以調整爲如下的過程:
首先,Servlet 接收到請求之後,可能首先需要對請求攜帶的數據進行一些預處理;
接着,Servlet 線程將請求轉交給一個異步線程來執行業務處理,線程本身返回至容器,此時 Servlet 還沒有生成響應數據,異步線程處理完業務以後,可以直接生成響應數據(異步線程擁有 ServletRequest 和 ServletResponse 對象的引用),或者將請求繼續轉發給其它 Servlet。
如此一來, Servlet 線程不再是一直處於阻塞狀態以等待業務邏輯的處理,而是啓動異步線程之後可以立即返回。
開啓功能:
異步處理特性可以應用於 Servlet 和過濾器兩種組件,由於異步處理的工作模式和普通工作模式在實現上有着本質的區別,因此默認情況下,Servlet 和過濾器並沒有開啓異步處理特性,如果希望使用該特性,則必須按照如下的方式啓用:
1、對於使用傳統的部署描述文件 (web.xml) 配置 Servlet 和過濾器的情況,Servlet 3.0 爲 和 標籤增加了 子標籤,該標籤的默認取值爲 false,要啓用異步處理支持,則將其設爲 true 即可。以 Servlet 爲例,其配置方式如下所示:
<servlet>
<servlet-name>DemoServlet</servlet-name>
<servlet-class>footmark.servlet.Demo Servlet</servlet-class>
<async-supported>true</async-supported>
</servlet>
2、對於使用 Servlet 3.0 提供的 @WebServlet 和 @WebFilter 進行 Servlet 或過濾器配置的情況,這兩個註解都提供了 asyncSupported 屬性,默認該屬性的取值爲 false,要啓用異步處理支持,只需將該屬性設置爲 true 即可。以 @WebFilter 爲例,其配置方式如下所示:
@WebFilter(urlPatterns = "/demo",asyncSupported = true)
public class DemoFilter implements Filter{...}
一個簡單的模擬異步處理的 Servlet 示例如下:
@WebServlet(urlPatterns = "/demo", asyncSupported = true)
public class AsyncDemoServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
out.println("進入Servlet的時間:" + new Date() + ".");
out.flush();
//在子線程中執行業務調用,並由其負責輸出響應,主線程退出
AsyncContext ctx = req.startAsync();
new Thread(new Executor(ctx)).start();
out.println("結束Servlet的時間:" + new Date() + ".");
out.flush();
}
}
public class Executor implements Runnable {
private AsyncContext ctx = null;
public Executor(AsyncContext ctx){
this.ctx = ctx;
}
public void run(){
try {
//等待十秒鐘,以模擬業務方法的執行
Thread.sleep(10000);
PrintWriter out = ctx.getResponse().getWriter();
out.println("業務處理完畢的時間:" + new Date() + ".");
out.flush();
ctx.complete();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Servlet 3.0 還爲異步處理提供了一個監聽器,使用
AsyncListener 接口表示。它可以監控如下四種事件:
1. 異步線程開始時,調用 AsyncListener 的 onStartAsync(AsyncEvent event) 方法;
2. 異步線程出錯時,調用 AsyncListener 的 onError(AsyncEvent event) 方法;
3. 異步線程執行超時,則調用 AsyncListener 的 onTimeout(AsyncEvent event) 方法;
4. 異步執行完畢時,調用 AsyncListener 的 onComplete(AsyncEvent event) 方法;
要註冊一個AsyncListener,只需將準備好的 AsyncListener 對象傳遞給 AsyncContext 對象的 addListener() 方法即可,如下所示:
AsyncContext ctx = req.startAsync();
ctx.addListener(new AsyncListener() {
public void onComplete(AsyncEvent asyncEvent) throws IOException {
// 做一些清理工作或者其他
}
...
});