目錄
生命周期簡介
生命周期測試
load-on-startup
補充:defaultServlet
Servlet 的繼承結構
1. 頂級的 Servlet 接口
2. 抽線的類 GenericServlet
3. HttpServlet 抽象類
4. 自定義 Servlet
補充:
完!
生命周期簡介
什么是生命周期?
? ? ? ? 應用程序中的對象,不僅在空間上有層次結構的關系,在實踐上也會因為處于運行過程中的不同階段,而表現出不同狀態和不同行為,這就是對象的生命周期
? ? ? ? 簡單的敘述生命周期:就是對象在開始創建,到最后銷毀的過程。
Servlet 容器:
? ? ? ? Servlet 對象是 Servlet 容器創建的,生命周期方法都是由容器(我們目前使用 Tomcat)調用的。這一點和我們之前所編寫的代碼,由很大的不同!在今后的學習中,我們越來越多的對象,都要交給容器或框架來創建,越來越多的方法,都要由容器或框架來調用。
? ? ? ? 我們作為程序員,要盡可能的將經歷放在業務邏輯的實現上!
Servlet 主要的生命周期執行特點:
生命周期測試
還在我們前面的項目工程中,開發一個測試代碼:
package com.zzz.servlet;import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;/** @author zzr* @date: 2025/07/04 21:24* @description: 測試 servlet 的生命周期*/
@WebServlet("/servletLifeCycle")
public class servletLifeCycle extends HttpServlet {public servletLifeCycle() {System.out.println("構造器");}@Overridepublic void init() throws ServletException {System.out.println("初始化");}@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("服務");}@Overridepublic void destroy() {System.out.println("銷毀");}
}
在四處打印的地方打上斷點:
debug 模式運行
為瀏覽器配置好 url 后,回車,發現代碼卡在了“構造器”的斷點處
一步一步向后運行代碼
此時并沒有銷毀。并且,當我們在瀏覽器,刷新重新打開的時候,就只會再打印“服務”了~
停止項目后,會打印“銷毀”
結論:
? ? ? ? 通過生命周期的測試,我們發現:Servlet 對象在容器中是單例的。但,容器又是需要并發的,處理用戶請求的,每個請求在容器中,都會開啟一個線程。
? ? ? ? 多個線程可能會使用相同的 Servlet 對象,但 Servlet 對象在容器中是單例的,所以 Servlety 的成員變量,在多個線程之中也是共享的 ==》 非常非常不建議,在 service 中修改成員變量,在并發請求的時候,會引發線程安全問題~
load-on-startup
我們在前面的 @WebServlet 提到過這個成員變量~
其值是一個數字,含義是:tomcat 在啟動時,實例化該 servlet 的順序。(如果順序號沖突了,tomcat 會自動協調啟動順序~)
<servlet><servlet-name>servletLifeCycle</servlet-name><servlet-class>com.zzz.servlet.servletLifeCycle</servlet-class>
<!--默認值是 -1 含義是 tomcat 啟動時不會實例化該 servlet其他正整數,例如 15,含義是 在 tomcat啟動時,實例化該 servlet 的順序
--><load-on-startup>15</load-on-startup></servlet><servlet-mapping><servlet-name>servletLifeCycle</servlet-name><url-pattern>/servletLifeCycle</url-pattern></servlet-mapping>
將 servletLifeCycle 中的 @WebServlet 注釋刪掉,取消所有斷點,直接運行程序:
發現自動就為我們準備好了構造器和初始化~
當然也可以在 @WebServlet 中設置 loadOnStartup 的值
注意:我們可以在 tomcat 文件夾下的 conf/web.xml 進行查找,發現有些序號已經占用了,1 - 5 號都已經被占用,我們如果要使用,最好不要重復 1 - 5?號~(這個序號就算不連貫也是可以的,tomcat 會自己匹配~)
補充:defaultServlet
defaulyServlet 是用于加載靜態資源的 servlet,默認隨服務器啟動,默認啟動序號為 1
Servlet 的繼承結構
1. 頂級的 Servlet 接口
源碼如下:
public interface Servlet {void init(ServletConfig var1) throws ServletException;ServletConfig getServletConfig();void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;String getServletInfo();void destroy();
}
Servlet 規范接口,所有的 servlet 必須實現
? ? ? ? init:
? ? ? ? ? ? ? ? 初始化方法,容器在構造 servlet 對象后,自動調用的方法,容器負責實例化一個 ServletConfig 對象,并在調用該方法的時候傳入。
? ? ? ? ? ? ? ? ServletConfig 對象可以為 servlet 對象提供初始化參數
? ? ? ? getServletConfig:
? ? ? ? ? ? ? ? 獲取 ServletConfig 對象的方法,后續可以通過該對象獲取 servlet 初始化參數
? ? ? ? service:
? ? ? ? ? ? ? ? 處理請求并做出響應的服務方法,每次請求產生的時候,都是由容器調用。
? ? ? ? ? ? ? ? 容器創建一個 ServletRequest 對象喝 ServletResponse 對象,容器在調用 service 方法時候,傳入這兩個對象。
? ? ? ? getservletInfo:
? ? ? ? ? ? ? ? 獲取 servletInfo 信息的方法
? ? ? ? destory:
? ? ? ? ? ? ? ? servlet 實例在銷毀之前調用的方法,用用作資源釋放
2. 抽線的類 GenericServlet
源碼如下:
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {private static final long serialVersionUID = 1L;private transient ServletConfig config;public GenericServlet() {}public void destroy() {}public String getInitParameter(String name) {return this.getServletConfig().getInitParameter(name);}public Enumeration<String> getInitParameterNames() {return this.getServletConfig().getInitParameterNames();}public ServletConfig getServletConfig() {return this.config;}public ServletContext getServletContext() {return this.getServletConfig().getServletContext();}public String getServletInfo() {return "";}public void init(ServletConfig config) throws ServletException {this.config = config;this.init();}public void init() throws ServletException {}public void log(String message) {ServletContext var10000 = this.getServletContext();String var10001 = this.getServletName();var10000.log(var10001 + ": " + message);}public void log(String message, Throwable t) {this.getServletContext().log(this.getServletName() + ": " + message, t);}public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;public String getServletName() {return this.config.getServletName();}
}
?GenericServlet 抽象類,是對 Servlet 接口一些固定功能的粗糙實現,以及對 service 方法的再次抽象聲明,并定義了一些其他相關功能方法。
private transit ServletConfig config:
? ? ? ? 初始化配置對象作為屬性(transit 是一個特殊的關鍵字。當對象被序列化時,被 transit 修飾的變量不會被序列化,也就是不會被持久化存儲或通過網絡傳輸)
public GenericServle:
? ? ? ? 構造器,為了滿足繼承而準備
public void destory:
? ? ? ? 將 Servlet 中的抽象方法,重寫為普通方法,在方法內部中沒有任何實現的代碼,稱為 destory 的平庸實現 ==》?讓子類可根據需要選擇是否重寫,實現銷毀相關邏輯
public void init() {
? ? ? ? this.config() = config;
? ? ? ? this.init();
}
? ? ? ? tomcat 在調用 init 方法時,會讀取配置信息進入一個 ServletConfig 對象,并將該對象傳入 init 方法。此方法將 config 對象存儲為當前的屬性,并且調用了重載的無參的 init 方法
public void inti:
? ? ? ? 重載的初始化方法,即我們重寫初始化方法對應的方法。
public ServletConfig getServletConfig:
? ? ? ? 返回 ServletConfig 的方法
public abstract void service:
? ? ? ? 再次抽象聲明 service 方法
3. HttpServlet 抽象類
在這個抽象里,側重于 service 方法的處理
源代碼較長,此處選部分重要的進行理解:
private static final String METHOD_DELETE = "DELETE";private static final String METHOD_HEAD = "HEAD";private static final String METHOD_GET = "GET";private static final String METHOD_OPTIONS = "OPTIONS";private static final String METHOD_POST = "POST";private static final String METHOD_PUT = "PUT";private static final String METHOD_TRACE = "TRACE";
上述屬性,用于定義常見請求方式名的常量值
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {HttpServletRequest request;HttpServletResponse response;try {request = (HttpServletRequest)req;response = (HttpServletResponse)res;} catch (ClassCastException var6) {throw new ServletException(lStrings.getString("http.non_http"));}this.service(request, response);}
request = (HttpServletRequest)req 和 response = (HttpServletResponse)res 都是參數的父轉子操作(子類的方法屬性更多一些~) 再調用重載的 service 方法
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String method = req.getMethod();if (method.equals("GET")) {this.doGet(req, resp);} else if (method.equals("HEAD")) {this.doHead(req, resp);} else if (method.equals("POST")) {this.doPost(req, resp);} else if (method.equals("PUT")) {this.doPut(req, resp);} else if (method.equals("DELETE")) {this.doDelete(req, resp);} else if (method.equals("OPTIONS")) {this.doOptions(req, resp);} else if (method.equals("TRACE")) {this.doTrace(req, resp);} else {resp.sendError(501, errMsg);}}
在 protected 修飾的 service 方法中,先是獲取了請求的方式,然后根據請求方式,調用對應的 do*** 方法
do*** 方法大同小異,這里以 doGet 為例:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String msg = lStrings.getString("http.method_get_not_supported");this.sendMethodNotAllowed(req, resp, msg);}
先獲取對應的字符串,然后調調用 sendMethodNotAllowed 方法,即 sendError 方法,故意響應 405 請求方式不允許的信息。
4. 自定義 Servlet
public class servlet1 extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("servlet1 執行了");}
}
在自定義的 Servlet 中,實現接收用戶請求信息,然后做出資源響應
補充:
如果我們自定義的 servlet 中,沒有重寫 service 方法,就會運行父類的 HttpServlet 中的 service 方法,在父類的 service 方法中,就會執行默認的 doGet 和 doPost 方法 ==》 響應 405
我們也可以自定義的 service 方法中,不重寫 service 方法,直接重寫 doGet 和 doPost 方法~
有些從程序員,推薦在自定義的 servlet 中重寫 do*** 方法處理請求,理由:父類中的 service 方法中可能做了一些處理,如果我們直接重寫 service 方法,父類中的 service 方法中的一些處理可能會失效。
但是,目前觀察,直接重寫 service 并不會有什么問題~
后續使用 SpringMVC 框架之后,我們則無需繼承 HttpServlet,處理請求的方法也無需是 do*** 和 service 了
補充:在此處,我們自定義的 servlet 中,要么重寫 service 方法,要么重寫 do*** 方法~
如果同時重寫了 service 和 do*** 方法,service 優先