背景:面試中很基礎的一個問題,所以有必要好好整理一番。
Servlet體系結構是建立在 Java 多線程機制上的,它的生命周期由 Web 容器負責。
當客戶端第一次請求某個 Servlet 時,Servlet 容器將會根據 web.xml 的配置文件實例化這個 Servlet 類。當有新的客戶端請求該 Servlet 時,一般不會再實例化該 Servlet 類。
當有多個請求時,Servlet 容器會起多個線程來訪問同一個 Servlet 實例的 service() 方法,如果該 Servlet 實例中有共享的實例變量,需要注意多線程安全問題。
生命周期
Servlet 生命周期定義了 Servlet 從創建到毀滅的整個過程,總共分為四個步驟。
調用 init() 方法初始化
調用 service() 方法來處理客戶端的請求
調用 destroy() 方法釋放資源,標記自身為可回收
被垃圾回收器回收
init() 方法
init 方法被設計成只調用一次。它在第一次創建 Servlet 時被調用,用於 Servlet的初始化,初始化的數據,可以在整個生命周期中使用。
在下列時刻Servlet容器裝載Servlet,共三種情況:
1,Servlet容器啟動時自動裝載某些Servlet,實現它只需要在web.XML文件中的<Servlet></Servlet>之間添加如下代碼:
<loadon-startup>1</loadon-startup>
說明:
在servlet的配置當中,<load-on-startup>5</load-on-startup>的含義是:
標記容器是否在啟動的時候就加載這個servlet。
當值為0或者大於0時,表示容器在應用啟動時就加載這個servlet;
當是一個負數時或者沒有指定時,則指示容器在該servlet被選擇時才加載。
正數的值越小,啟動該servlet的優先級越高。
2,在Servlet容器啟動后,客戶首次向Servlet發送請求
3,Servlet類文件被更新后,重新裝載Servlet
Servlet被裝載后,Servlet容器創建一個Servlet實例並且調用Servlet的init()方法進行初始化。在Servlet的整個生命周期內,init()方法只被調用一次。
初始化階段步驟:
Ø Servlet容器加載servlet類,把它的. Class文件中的數據讀到內存中。
Ø Servlet容器創建servletConfig對象。servletConfig對象包含了servlet的初始化配置信息。此外servlet容器還會使得servletConfig對象與當前的web應用的servletContext對象關聯。
Ø Servlet容器創建servlet對象。
Ø Servlet容器調用servlet對象的init(ServletConfig config)方法。
通過初始化步驟,創建了servlet對象和servletConfig對象,並且servlet對象與servletConfig對象關聯,而servletConfig對象又與當前對象的servletContext對象關聯。當servlet容器完成servlet后,servlet對象只要通過getServletContext()方法就能得到web應用的servletContext對象。
service() 方法
service() 方法是執行實際任務的主要方法。 Servlet 容器(Tomcat、Jetty等)調用 service() 方法來處理來自客戶端(瀏覽器)的請求,並把相應結果返回給客戶端。
每次 Servlet 容器接收到一個 Http 請求, Servlet 容器會產生一個新的線程並調用 Servlet實例的 service 方法。 service 方法會檢查 HTTP 請求類型(GET、POST、PUT、DELETE 等),並在適當的時候調用 doGet、doPost、doPut、doDelete 方法。所以,在編碼請求處理邏輯的時候,我們只需要關注 doGet()、或doPost()的具體實現即可。
對於Tomcat來說,它會將傳遞過來的參數放在一個Hashtable中,該Hashtable的定義是:
private Hashtable<String String[]> paramHashStringArray = new Hashtable<String String[]>();
這是一個String-->String[]的鍵值映射。
HashMap線程不安全的,Hashtable線程安全。
destroy() 方法
destroy() 方法也只會被調用一次,在 Servlet 生命周期結束時調用。destroy() 方法主要用來清掃“戰場”,執行如關閉數據庫連接、釋放資源等行為。
調用 destroy 方法之后,servlet 對象被標記為垃圾回收,等待 JVM 的垃圾回收器進行處理。

引申
Servlet何時被創建:
1,默認情況下,當WEB客戶第一次請求訪問某個Servlet的時候,WEB容器將創建這個Servlet的實例。
2,當web.xml文件中如果<servlet>元素中指定了<load-on-startup>子元素時,Servlet容器在啟動web服務器時,將按照順序創建並初始化Servlet對象。
注意:在web.xml文件中,某些Servlet只 有<serlvet>元素,沒有<servlet-mapping>元素,這樣我們無法通過url的方式訪問這些 Servlet,這種Servlet通常會在<servlet>元素中配置一個<load-on-startup>子元素,讓容 器在啟動的時候自動加載這些Servlet並調用init()方法,完成一些全局性的初始化工作。
Web應用何時被啟動:
1,當Servlet容器啟動的時候,所有的Web應用都會被啟動
2,控制器啟動web應用
Servlet與JSP的比較:
有許多相似之處,都可以生成動態網頁。
JSP的優點是擅長於網頁制作,生成動態頁面比較直觀,缺點是不容易跟蹤與排錯。
Servlet是純Java語言,擅長於處理流程和業務邏輯,缺點是生成動態網頁不直觀。
Servlet工作原理:
首先簡單解釋一下Servlet接收和響應客戶請求的過程,首先客戶發送一個請求,Servlet是調用service()方法對請求進行響應 的,通過源代碼可見,service()方法中對請求的方式進行了匹配,選擇調用doGet,doPost等這些方法,然后再進入對應的方法中調用邏輯層 的方法,實現對客戶的響應。在Servlet接口和GenericServlet中是沒有doGet,doPost等等這些方法 的,HttpServlet中定義了這些方法,但是都是返回error信息,所以,我們每次定義一個Servlet的時候,都必須實現doGet或 doPost等這些方法。
每一個自定義的Servlet都必須實現Servlet的接口,Servlet接口中定義了五個方法,其中比較重要的三個方法涉及到 Servlet的生命周期,分別是上文提到的init(),service(),destroy()方法。GenericServlet是一個通用的,不 特定於任何協議的Servlet,它實現了Servlet接口。而HttpServlet繼承於GenericServlet,因此 HttpServlet也實現了Servlet接口。所以我們定義Servlet的時候只需要繼承HttpServlet即可。
Servlet接口和GenericServlet是不特定於任何協議的,而HttpServlet是特定於HTTP協議的類,所以 HttpServlet中實現了service()方法,並將請求ServletRequest,ServletResponse強轉為 HttpRequest和HttpResponse。
public void service(ServletRequest req,ServletResponse res) throws ServletException,IOException { HttpRequest request; HttpResponse response; try { req = (HttpRequest)request; res = (HttpResponse)response; }catch(ClassCastException e) { throw new ServletException("non-HTTP request response"); } service(request,response); }
代碼的最后調用了HTTPServlet自己的service(request,response)方法,然后根據請求去調用對應的doXXX方法,因為HttpServlet中的doXXX方法都是返回錯誤信息,
protected void doGet(HttpServletRequest res,HttpServletResponse resp) throws ServletException,IOException { String protocol = req.getProtocol(); String msg = IStrings.getString("http.method_get_not_supported"); if(protocol.equals("1.1")) { resp.sendError(HttpServletResponse.SC.METHOD.NOT.ALLOWED,msg); } esle { resp.sendError(HttpServletResponse.SC_BAD_REQUEST,msg); } }
