解讀 Servlet 源碼:GenericServlet,ServletConfig,ServletContext 每博一文案 人活著,就得隨時準備經受磨難。他已經看過一些書,知道不論是普通人還是了不起的人,都要在自己的一生中經歷許多磨難。磨難使人堅強。 人和社會、一切鬥爭的總結局也許都是中庸而已。 ...
解讀 Servlet 源碼:GenericServlet,ServletConfig,ServletContext
每博一文案
人活著,就得隨時準備經受磨難。他已經看過一些書,知道不論是普通人還是了不起的人,都要在自己的一生中經歷許多磨難。磨難使人堅強。
人和社會、一切鬥爭的總結局也許都是中庸而已。與其認真、不如隨便、採菊東籬下、悠然見南山。有錢就尋一醉、無錢就尋一睡、與過無爭、隨遇而安。
hellip;…人哪,活著是這麼的苦!一旦你從幸福的彼岸被拋到苦難的此岸,你真是處處走頭無路;而現在你才知道,在天堂與地獄之間原來也只有一步之遙!
倘若流落在他鄉異地,生活中的一切都將失去保障,得靠自己一個人去對付冷酷而嚴峻的現實了
青年,青年,無論受怎樣的挫折和打擊,都要咬著牙關挺住,因為你們完全有機會重建生活;只要不灰心喪氣,每一次挫折就只不過是通往新境界的一塊普通的絆腳石,而絕不會置人於死命。
你永遠要寬恕眾生,不論他有多壞,甚至他傷害過你,你一定要放下,才能得到真正的快樂。
也許人生僅有那麼一兩個輝煌的瞬間————甚至一生都可能在平淡無奇中度過......不過,每個人的生活同樣也是一個世界,即是最平凡的人,也得要為他那個世界的存在而戰鬥。這個意義上來說,在這些平凡的世界里,也沒有一天是平靜的。因此,大多數
普通人不會像飄飄欲仙的老莊,時常把自己看作是一粒塵埃————儘管地球在浩渺的宇宙中也只不過是一粒塵埃罷了。
——————《平凡的世界》
@
目錄- 解讀 Servlet 源碼:GenericServlet,ServletConfig,ServletContext
1. Servlet對象的生命周期
什麼是Servlet 對象的生命周期 ?
Servlet 對象的生命周期表示:一個Servlet對象從出生在最後的死亡,整個過程是怎樣的 。
Servlet對象是由誰來維護的?
- Servlet 對象的創建,Servlet對象上方法的調用,以及Servlet 對象的銷毀,JavaWeb程式員都無權干預的。
- Servlet 對象的生命周期是由 Tomcat 伺服器(web Server)全權負責的
- Tomcat 伺服器通常又被我們稱之為 WEB容器 (WEB Container) 。WEB 容器來管理 Servlet對象的死活
我們自己new的Servlet對象受WEB容器的管理嗎?
我們自己 new 的 Servlet 對象是不受 WEB容器 管理的。
因為: WEB容器 創建的 Servelt 對象,這些Servlet 對象都會被放到一個集合當中(HashMap) ,只有放到這個 HashMap 集合中的 Servlet 才能夠被 WEB容器管理,自己 new 的 Servlet 對象不會被 WEB容器管理,因為我們自己 new 的Servlet對象並不沒有存放到 WEB 容器當中。
WEB容器底層應該有一個 HashMap這樣的集合,在這個集合當中存儲了Servlet對象和請求路徑之間的關係。
1.1 Servlet 五 個方法的調用周期
Servlet 必須重寫的五方法分別為:init(),service(ServletRequest request, ServletResponse response),getServletConfig(),getServletInfo(),destroy(),還有一個無參構造器 什麼時候創建的,什麼時候調用的,什麼時候銷毀的。
package com.RainbowSea.servlet;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
public class AServlet implements Servlet {
@Override
public void init(ServletConfig config) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
執行如下代碼,啟動 Tomcat 伺服器:並訪問該AServlet ,通過在瀏覽器其當中輸入: http://127.0.0.1:8080/blog01/A
package com.RainbowSea.servlet;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
public class AServlet implements Servlet {
public AServlet(){
System.out.println("AServlet 的無參構造器的調用");
}
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("AServlet 中的 init 的調用執行");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
System.out.println("AServlet 中的 service 的方法的調用執行");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
System.out.println("AServlet 中的 destroy 方法的執行調用");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<!-- 兩個 name 值要保持一致-->
<servlet-name>AServlet</servlet-name>
<servlet-class>com.RainbowSea.servlet.AServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AServlet</servlet-name>
<!-- 註意: / 開始-->
<url-pattern>/A</url-pattern>
</servlet-mapping>
</web-app>
執行結果如下:
重點:
- 當我們在預設普通配置的情況下,啟動Tomcat 伺服器的時候,我們編寫的 AServlet 對象並沒有被實例化(因為並沒有調用我們 AServlet 構造器創建對象)。
- 當用戶訪問該資源,第一次向我們的 AServlet 發送請求的時候。我們的 Servlet對象就被實例化了(這裡是 AServlet 的構造器被執行了,並且執行的是該 AServlet 無參構造器。 )
- 之後,當 Servlet 對象被創建出來了,也就是這裡的 AServlet 對象,Tomcat 伺服器馬上就調用了 AServlet 對象當中的 init()對象方法 ,(註意:init()方法在執行的時候,我們的 Servlet(這裡的 AServlet)對象就已經存在了,已經被創建出來了,因為 init() 是對象方法,需要通過創建對象才能被調用(特殊的 :反射除外))。
- 用戶發送第一次請求的時候,init()方法執行完,Tomcat 伺服器馬上調用了 Servlet (這裡的AServlet 對象)當中的 servelt() 方法。
- 註意:當用戶繼續發送第二次請求(刷新該,頁面的資源,重新發送新的請求)。或者第三次,或者第四次請求的時候,Servlet對象並沒有新建,還是使用之前創建好的Servlet對象,直接調用該Servlet對象的service方法結果如下:
- 根據上述結果:我們可以知道如下信息:
- Servlet 對象使單例的 (單實例的。但是要註意:Servlet 對象使單實例的,但是 Servlet 類並不符合單例模式 的設計。因為真正的單例模式的構造器private私有的 ,所以我們稱之為 假單例 ,之所以單例是因為 Servelt 對象的創建,我們 javaWeb程式員是管不著的,這個對象的創建只能是 Tomcat 伺服器或其他伺服器來說的算的。Tomcat 只會創建一個,所以導致了單例,但是屬於假單例。)
- 無參構造器,只會執行一次,該無參構造器是通過反射機制調用的(通過從 web.xml 中的配置信息獲取到其中對應類的:全限定類名 創建對象),
- init() 方法只在第一次用戶發送請求的時候執行,init對象方法也只被 Tomcat 伺服器調用一次。
- 只要用戶發送一次請求:service() 方法必然會被 Tomcat 伺服器調用一次,發送 100 次請求,servlect() 方法就會被調用 100次。
- 我們關閉 Tomcat 伺服器,控制臺上顯示如下結果 :
從上述控制臺上顯示的結果,我們可以知道:當我們關閉伺服器的時候 destroy()方法被調用了
- destroy()方法是在什麼時候被調用的 ?
在伺服器關閉的時候。
因為伺服器關閉的時候:要銷毀 AServlet 對象的記憶體信息。
所以伺服器在銷毀 AServlet 對象記憶體之前,Tomcat 伺服器會自動調用 AServlet 對象中的 destroy() 方法。
問題:destroy() 方法調用的時候,Servlet 對象銷毀了還是沒有銷毀 ???
答:destroy() 方法執行的時候,AServlet 對象還在,沒有被銷毀,因為 destroy() 方法是對象方法。調用該方法的話需要通過對象才能調用的(反射除外),destroy()方法執行結束之後,AServlet 對象的記憶體才會被 Tomcat 釋放。
註意點: 對應 Servlet 類我們不要編寫構造器。
當我們Servlet 類中編寫了一個有參數的構造器,那麼如果我們沒有手動再編寫一個無參構造器的話,無參構造器就會消失。
如果一個 Servlet 無參構造器消失的會,如何:結果如下:
報錯:500 錯誤。500 是一個 HTTP 協議的錯誤狀態碼。 500 一般情況下是因為伺服器端的 java 程式出現了異常 。(伺服器端的錯誤基本上都是 500 錯誤,伺服器內部錯誤)
- 如果一個 Servlet 類沒有無參構造器的話,會導致 500 錯誤,反射機制無法通過 無參構造器創建對象 。導致 無法實例化 Servlet 對象。
- 所以,一定要註意:在 Servlet 開發當中,不建議 程式員來定義構造器,因為定義不當,一不小心就會導致無法實例化 Servlet對象了。
思考:Servlet 的無參數構造器是在對象第一次創建的時候執行的,並且只會執行一次。而 init()
方法也是在對象第一次拆創建的時候執行的,並且也只會執行一次。那麼這個無參構造器可以代替 init()方法嗎 ?
答:不能。
Servlet 規範中有要求,作為 javaWeb 程式員,編寫 Servlet 類的時候,不建議手動編寫構造器,因為編寫構造器,很容易讓無參數構造器消失。這個操作可能會導致 Servlet 對象無法實例化,所以 init() 方法是有存在的必要的。
1.2 Servlet 常用的三個方法使用總結
package com.RainbowSea.servlet;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
public class AServlet implements Servlet {
// init 被翻譯為初始化
// init 方法只會被執行一次,基本上和 Servlet構造器的調用同時執行,在Servlet 對象第一次被創建只會執行
// init 方法通常是完成初始化操作的。
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("AServlet is init method execute!");
}
// service 方法:是處理用戶請求的核心方法
// 只要用戶發送一次請求,service 方法必然會執行一次
// 發送100次請求,service方法執行100次
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
System.out.println("AServlet is service method execute");
}
// destroy ()方法也是只執行一次
// Tomcat 伺服器在銷毀AServlet 對象之前會調用一次destroy 方法
// destroy()方法在執行的時候,AServlet 對象的記憶體還沒有被銷毀,即將被銷毀,因為destroy 是對象方法,需要通過對象調用
// destroy 方法中可以編寫銷毀前的準備
// 比如:伺服器關閉的時候,AServelt 對象開啟了一些資源,這些資源可能是流,也可能是資料庫連接
// 那麼關閉伺服器的時候,要關閉這些流,關閉這些資料庫連接,那麼這些關閉資源的代碼舊可以寫到destroy()
@Override
public void destroy() {
System.out.println("AServlet is destroy method execute");
}
}
關於Servlet類中方法的說明:
- 無參構造器:只會執行調用一次。
- init() 方法:init 被翻譯為初始化,init ()方法只會被執行一次;基本上和 Servlet構造器的調用同時執行;在Servlet 對象第一次被創建只會執行。init 方法通常是完成初始化操作的。
- service() 方法:service() 方法是處理用戶請求的核心方法。只要用戶發送一次請求,service 方法必然會執行一次;發送100次請求,service方法執行100次。
- destroy() 方法:destroy 方法中可以編寫銷毀前的準備;比如:伺服器關閉的時候,AServelt 對象開啟了一些資源,這些資源可能是流,也可能是資料庫連接;那麼關閉伺服器的時候,要關閉這些流,關閉這些資料庫連接,那麼這些關閉資源的代碼舊可以寫到destroy()。
init、service、destroy方法中使用最多的是哪個方法?
使用最多就是service()方法,service方法是一定要實現的,因為service方法是處理用戶請求的核心方法。
什麼時候使用init方法呢?
init方法很少用;通常在init方法當中做初始化操作,並且這個初始化操作只需要執行一次。例如:初始化資料庫連接池,初始化線程池....
什麼時候使用destroy方法呢?
destroy方法也很少用;通常在destroy方法當中,進行資源的關閉。馬上對象要被銷毀了,還有什麼沒有關閉的,抓緊時間關閉資源。還有什麼資源沒保存的,抓緊時間保存一下。
Servlet對象更像一個人的一生:
- Servlet的無參數構造方法執行:標志著你出生了。
- Servlet對象的init方法的執行:標志著你正在接受教育。
- Servlet對象的service方法的執行:標志著你已經開始工作了,已經開始為人類提供服務了。
- Servlet對象的destroy方法的執行:標志著臨終。有什麼遺言,抓緊的。要不然,來不及了。
1.3 補充:讓啟動Tomcat 伺服器的時候會調用構造器
從上述內容我們知道了,當 Tomcat 伺服器啟動的時候,我們的構造器實際上還沒有調用,也就是對象還沒有被實例化創建出來。但是我們想在啟動 Tomcat 伺服器的時候就調用構造器。可以使用如下方式:
在我們想讓Tomcat 伺服器啟動的時候調用哪個類的構造器,就在該類當中的 web.xml
加上如下:標簽 <load-on-startup>(填寫數值)</load-on-startup>
: 的作用就是啟動伺服器的時候就會創建該對象:數值越小的越先被創建
<!--<load-on-startup> 的作用就是啟動伺服器的時候就會創建該對象:數值越小的越先被創建-->
<load-on-startup>2</load-on-startup>
舉例:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<!-- 兩個 name 值要保持一致-->
<servlet-name>AServlet</servlet-name>
<servlet-class>com.RainbowSea.servlet.AServlet</servlet-class>
<!-- 的作用就是啟動伺服器的時候就會創建該對象:數值越小的越先被創建-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>AServlet</servlet-name>
<!-- 註意: / 開始-->
<url-pattern>/A</url-pattern>
</servlet-mapping>
<servlet>
<!-- 兩個 name 保持一致-->
<servlet-name>BServlet</servlet-name>
<servlet-class>com.RainbowSea.servlet.BServlet</servlet-class>
<!-- 的作用就是啟動伺服器的時候就會創建該對象:數值越小的越先被創建-->
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>BServlet</servlet-name>
<!-- 註意 / 開始-->
<url-pattern>/B</url-pattern>
</servlet-mapping>
</web-app>
2. GenericServlet
2.1 適配器
我們編寫一個Servlet 類直接實現了 這個 Servlet 介面 存在什麼缺點沒有:?
有的:我們編寫的 Servlet 類實現了該 Servlet 介面中所有的方法。但是我們其實只需要其中的 service()方法 ,其他方法大部分情況下是不需要使用的。簡單粗暴的實現了我們所有的方法,但是其中大部分的方法我們是不需要的,這樣就顯的我們的代碼很醜陋了:
所以:我們不想要重寫所以的方法,而是重寫我們需要用的方法。這樣該怎麼做呢?
我們可以使用 23種設計模式中的一種:適配器模式(Adapter) 。
適配器模式的理解:舉例
比如:如果我們的手機充電時直接插入到 220V 的電壓上,手機會直接報廢的,因為手機無法接受如此高的電壓,怎麼辦呢?
我們可以該手機配備一個電源適配器——充電器,手機連接充電器(適配器),適配器連接 220V的電壓,這樣就問題解決了。同理的還有很多:電腦的充當器。充電熱水壺等等。
編寫一個 GenericServlet (abstract )抽象類適配器。
這個適配器是一個abstract
抽象類。:我們該類實現了 Servlet 介面中所有的方法,但是其中我們常用的方法 service() 定義為是抽象方法,因為如果不想實現的方法,可以定義將該方法定義為抽象方法就不用重寫了,而是交給繼承的子類重寫就好了。因為重寫方法只能存在於抽象類,介面當中,所以我們這個適配器就為了一個抽象類。這樣我們以後編寫的所以 Servlet 類就只需要繼承 GenericServlet 抽象類,並且只要重寫其中的 service() 抽象方法即可。但是我們又可以使用 Servlet 介面中的方法,因為我們的父類GenericServlet 抽象類 實現了 Servlet 介面中的所有方法。如有必要,我們編寫的Servlet 類也可以重寫GenericServlet 抽象類中的方法,該重寫的方法也是重寫Servlet 介面中的方法。
具體編寫如下:
abstract class GenericServlet 抽象類適配器
package com.RainbowSea.servlet;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
/**
* 編寫一個標準通用的Servlet
* 以後所有的Servlet 類都不要直接實現Servlet 介面了。
* 以後所有的Servlet 類都繼承 GenericServlet 類。
* GenericServlet 就是一個適配器
*/
public abstract class GenericServlet implements Servlet {
@Override
public void init(ServletConfig config) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
/**
* 抽象方法:這個方法最常用,所以要求子類必須實現Service 方法。
*/
@Override
public abstract void service(ServletRequest request, ServletResponse response) throws ServletException, IOException;
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
以後編寫的Servlet 都繼承該 GenericServlet 抽象類適配器
package com.RainbowSea.servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
public class AServlet extends GenericServlet {
/**
* 只需要重寫我們常用的 service 父類中的抽象方法即可
*/
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
System.out.println("AServlet 處理請求中....");
}
}
package com.RainbowSea.servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
public class BServlet extends GenericServlet {
/**
* 只需要重寫我們常用的 service 父類中的抽象方法即可
*/
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
System.out.println("BServlet 處理請求中....");
}
}
問題:提供了 GenericServlet 類,init()方法還好執行嗎?
答:還會執行,執行 GenericServlet 中被重寫的 init()方法。
問題:init()方法是誰調用的 ?
Tomcat 伺服器調用的。
問題:init()方法中的 ServletConfig 對象是誰創建的 ? 有是誰傳過來的?
是由 Tomcat伺服器 創建的,也是由 Tomcat 伺服器傳過來的。都是Tomcat 伺服器乾的。
Tomcat 伺服器先創建了 ServleConfig 對象,然後調用 init()方法,將ServleConfig 對象傳給了 init()方法。
Tomcat 執行init()方法的偽代碼 :
@Override
public void init(ServletConfig config) throws ServletException {
}
public class Tomcat {
public static void main(String[] args){
// .....
// Tomcat伺服器偽代碼
// 創建LoginServlet對象(通過反射機制,調用無參數構造方法來實例化LoginServlet對象)
Class clazz = Class.forName("com.bjpowernode.javaweb.servlet.LoginServlet");
Object obj = clazz.newInstance();
// 向下轉型
Servlet servlet = (Servlet)obj;
// 創建ServletConfig對象
// Tomcat伺服器負責將ServletConfig對象實例化出來。
// 多態(Tomcat伺服器完全實現了Servlet規範)
ServletConfig servletConfig = new org.apache.catalina.core.StandardWrapperFacade();
// 調用Servlet的init方法
servlet.init(servletConfig);
// 調用Servlet的service方法
// ....
}
}
2.2 模板方法設計模式
思考: init 方法中的ServileConfig 對象是小貓咪創建好的,這個ServletConfig對象目前在init 方法的參數上,屬於局部變數。那麼ServletConfig 對象肯定以後要在Service 方法中使用,怎麼才能保證ServletConfig 對象在Service方法中能夠使用呢?
答: 可以定義一個類的屬性,再通過 init 方法的,將該ServleConfig 賦值上去
具體代碼如下:
package com.RainbowSea.servlet;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
/**
* 編寫一個標準通用的Servlet
* 以後所有的Servlet 類都不要直接實現Servlet 介面了。
* 以後所有的Servlet 類都繼承 GenericServlet 類。
* GenericServlet 就是一個適配器
*/
public abstract class GenericServlet implements Servlet {
private ServletConfig config ;
// 這樣我們在我們的子類當中的 service()方法當中也可以使用 Tomcat 伺服器創建的
// ServletConfig 對象了
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
}
// 獲取該 config 對象值
public ServletConfig getConfig() {
return config;
}
@Override
public ServletConfig getServletConfig() {
return null;
}
/**
* 抽象方法:這個方法最常用,所以要求子類必須實現Service 方法。
*/
@Override
public abstract void service(ServletRequest request, ServletResponse response) throws ServletException, IOException;
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
舉例:Servale 類在 service ()方法當中獲取到該 ServletConfig 對象值,並使用
package com.RainbowSea.servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class AServlet extends GenericServlet {
/**
* 只需要重寫我們常用的 service 父類中的抽象方法即可
*/
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 設置 response 在瀏覽器頁面當中顯示的格式類型
// 註意需要在顯示之前,設置
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.print("AServlet 處理請求中...." + "<br>");
ServletConfig config = super.getConfig();
out.print("AServlet 當中 service 獲取到 Tomcat 伺服器創建的ServletConfig對象: " + config);
}
}
重點: 我們通過上述定義了一個 private ServletConfig config
成員屬性,可以獲取到Tomcat 創建的 ServletConfig 對象了,併在 service() 方法當中使用。但是存在一個問題:如果我們的子類要是重寫了,我們父類適配器 GenericServlet 類當中的 init()方法的話。因為多態性的緣故:在實際的運行過程中,調用的會是我們子類重寫的的 init() 方法,這樣導致的結果就,我們父類中的 private ServletConfig config
成員屬性的值無法賦值上為 null
了。因為我們父類當中的 config 成員屬性是通過父類當中的 init()方法賦值的,現在我們的子類重寫了父類的 init()方法。不會調用我們父類的 init()進行賦值操作了,從而導致為了 null 值。
解決方式:將我們父類當中的 init()方法被 final
修飾,這樣我們的子類就無法重寫 init()方法了。
public abstract class GenericServlet implements Servlet {
private ServletConfig config ;
// 這樣我們在我們的子類當中的 service()方法當中也可以使用 Tomcat 伺服器創建的
// ServletConfig 對象了
@Override
public final void init(ServletConfig config) throws ServletException {
this.config = config;
}
}
上述的解決方式仍然存在一個問題:就是如果我們的子類一定要重寫 init()方法該怎麼辦,現在你父類當中的 init()方法被 final 修飾了無法,被 子類重寫。
解決方式:
我們使用 23中設計模式當中的 模板方法設計模式:
因為子類想要重寫我們父類當中的 init()方法,那麼我們就在父類當中,再創建一個 無參數的init()方法(方法的重載),這個方法讓子類去重寫,但是我們父類當中還有一個 帶參數的 init(ServletConfig config) 方法,在該帶有參數的init()方法當中調用我們這個提供該子類重寫的 init()方法。具體代碼如下:
package com.RainbowSea.servlet;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
/**
* 編寫一個標準通用的Servlet
* 以後所有的Servlet 類都不要直接實現Servlet 介面了。
* 以後所有的Servlet 類都繼承 GenericServlet 類。
* GenericServlet 就是一個適配器
*/
public abstract class GenericServlet implements Servlet {
private ServletConfig config ;
// 這樣我們在我們的子類當中的 service()方法當中也可以使用 Tomcat 伺服器創建的
// ServletConfig 對象了,final 修飾的方法無法重寫
@Override
public final void init(ServletConfig config) throws ServletException {
// 賦值依舊存在,config 不會為 null
this.config = config;
// 調用子類重寫後的 init()方法
this.init();
}
// 用於提供給子類重寫
public void init() {
}
public ServletConfig getConfig() {
return config;
}
}
舉例:在AServlet 類當中重寫 init()方法,並且執行該重寫的方法:
package com.RainbowSea.servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class AServlet extends GenericServlet {
/**
* 只需要重寫我們常用的 service 父類中的抽象方法即可
*/
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 設置 response 在瀏覽器頁面當中顯示的格式類型
// 註意需要在顯示之前,設置
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.print("AServlet 處理請求中...." + "<br>");
ServletConfig config = super.getConfig();
out.print("AServlet 當中 service 獲取到 Tomcat 伺服器創建的ServletConfig對象: " + config);
}
// 重寫的父類當中的 init()無參方法
@Override
public void init() {
System.out.println("AServlet 重寫的 init()方法執行了");
}
}
2.3 補充:GenericServlet 不用我們自己編寫
註意:GenericServlet 抽象類適配器是不需要我們自己編寫的,Servlet 已經編寫好了,我們只需要使用就可以了。
上述是為了理解 GenericServlet 源碼,從而自己編寫的 GenericServlet 適配器的。如下是 Servlet 為我們編寫好的 GenericServlet 源碼,和我們上面自己編寫的設計結構是一樣的。
package javax.servlet;
import java.io.IOException;
import java.util.Enumeration;
public abstract class GenericServlet implements Servlet, ServletConfig,
java.io.Serializable {
private static final long serialVersionUID = 1L;
private transient ServletConfig config;
public GenericServlet() {
// NOOP
}
@Override
public void destroy() {
// NOOP by default
}
@Override
public String getInitParameter(String name) {
return getServletConfig().getInitParameter(name);
}
@Override
public Enumeration<String> getInitParameterNames() {
return getServletConfig().getInitParameterNames();
}
@Override
public ServletConfig getServletConfig() {
return config;
}
@Override
public ServletContext getServletContext() {
return getServletConfig().getServletContext();
}
@Override
public String getServletInfo() {
return "";
}
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
// NOOP by default
}
public void log(String message) {
getServletContext().log(getServletName() + ": " + message);
}
public void log(String message, Throwable t) {
getServletContext().log(getServletName() + ": " + message, t);
}
@Override
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
@Override
public String getServletName() {
return config.getServletName();
}
}
3. ServletConfig
從上面的知識我們知道了 : Tomcat 伺服器先創建了 ServletConfig 對象,然後調用init()方法,將ServletConfig對象傳給了init()方法。
先來一波,自問自答:
ServletConfig 是什麼?
package javax.servlet; 顯然 ServletConfig 是一個介面。
誰去實現了這個介面呢 ?
org.apache.catalina.core.StandardWrapperFacade 這個類實現了這個 ServletConfig 介面
StandardWrapperFacade 源碼如下:
package org.apache.catalina.core; import java.util.Enumeration; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; /** * Facade for the <b>StandardWrapper</b> object. * * @author Remy Maucherat */ public final class StandardWrapperFacade implements ServletConfig { // ----------------------------------------------------------- Constructors /** * Create a new facade around a StandardWrapper. * @param config the associated wrapper */ public StandardWrapperFacade(StandardWrapper config) { super(); this.config = config; } // ----------------------------------------------------- Instance Variables /** * Wrapped config. */ private final ServletConfig config; /** * Wrapped context (facade). */ private ServletContext context = null; // -------------------------------------------------- ServletConfig Methods @Override public String getServletName() { return config.getServletName(); } @Override public ServletContext getServletContext() { if (context == null) { context = config.getServletContext(); if (context instanceof ApplicationContext) { context = ((ApplicationContext) context).getFacade(); } } return context; } @Override public String getInitParameter(String name) { return config.getInitParameter(name); } @Override public Enumeration<String> getInitParameterNames() { return config.getInitParameterNames(); } }
Tomcat 伺服器實現了ServletConfig 介面。
思考: 如果 Tomcat 伺服器換成 了其他的伺服器,比如換成是 Jetty 伺服器,輸出 ServletConfig 對象的時候,還是這個結果嗎?
不一定是一樣的結果了,包含的類名可能和Tomcat 不一樣了,但是他們都實現了 ServletConfig 這個規範。
問題:ServletConfig對象是誰創建的,在什麼時候創建的?
Tomcat 伺服器(WEB伺服器) 創建了ServletConfig 對象,在創建Servlet 對象的時候,同時創建ServletConfig 對象.
問題:ServletConfig 介面到底是乾什麼的? 有什麼用?
Config 是 Configuration單詞的縮寫:n. 佈局,構造;配置
ServletConfig 對象翻譯為: Servlet 對象的配置信息對象。
一個Servlet對象就有一個配置信息對象:`web.xml <servlet></servlet>`
兩個Servlet 對象就有兩個配置信息對象。
ServletConfig對象中創建包裝了 web.xml <servlet></servlet> 標簽配置信息
: Tomcat 小喵咪解析web.xml 文件,將web.xml文件中<servlet><init-param></init-param></servlet>
標簽中的配置信息自動包裝到ServletConfig 對象當中 。
如下:註意:一個Servlet 對象就會有一個 <servlet></servlet>
配置信息標簽 。如下是 AServlet對象的 xml 配置信息。
<--以上是 <servlet-name>ConfigTestServlet</servlet-name> <init-param></init-param>
<--是初始化參數,這個初始化參數信息被小喵咪封裝到 ServletConfig 對象當中 -->
<servlet>
<servlet-name>AServlet</servlet-name>
<servlet-class>com.RainbowSea.servlet.AServlet</servlet-class>
<!-- 這裡是可以配置一個Servlet對象的初始化信息的 註意:這裡的配置對應的是上述<servlet-name>
為AServlet-->
<init-param>
<param-name>Driver</param-name>
<param-value>com.mysql.cj.jdbc.Driver</param-value>
</init-param>
<init-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/dbtest9</param-value>
</init-param>
<init-param>
<param-name>user</param-name>
<param-value>root</param-value>
</init-param>
</servlet>
3.1 ServletConfig 當中常用的四個方法
public String getServletName(); // 獲取該Servlet對象的名稱
public String getInitParameter(String name); // 通過 <param-name>user</param-name> 標簽當中的name 值獲取到對應 <param-value>root</param-value> 標簽當中的 value 值
public Enumeration<String> getInitParameterNames(); // 一次性獲取到該Servlet對象<init-param>標簽當中配置信息的 name 值
public ServletContext getServletContext(); // 獲取到ServletContext對象
// 以上的4個方法: 在自己的編寫的Servlet表當中也可以使用this,/super去調用(這個Servlet繼承了GenericServet)
3.1.2 通過ServletConfig 對象獲取到 web.xml 配置文件 標簽當中的配置信息
舉例:獲取到 web.xml 配置文件當中 :<servlet><init-param></init-param></servlet>
標簽中的配置信息。因為該配置信息是會被動包裝到ServletConfig 對象當中 。這裡我們分別獲取到 AServlet 對象和 BServlet 對象當中的配置信息。
對應web.xml 信息如下
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>AServlet</servlet-name>
<servlet-class>com.RainbowSea.servlet.AServlet</servlet-class>
<!-- 這裡是可以配置一個Servlet對象的初始化信息的 註意:這裡的配置對應的是上述<servlet-name>
為AServlet-->
<init-param>
<param-name>Driver</param-name>
<param-value>com.mysql.cj.jdbc.Driver</param-value>
</init-param>
<init-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/dbtest9</param-value>
</init-param>
<init-param>
<param-name>user</param-name>
<param-value>root</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>AServlet</servlet-name>
<url-pattern>/A</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>BServlet</servlet-name>
<servlet-class>com.RainbowSea.servlet.BServlet</servlet-class>
<init-param>
<param-name>password</param-name>
<param-value>root123</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>BServlet</servlet-name>
<url-pattern>/B</url-pattern>
</servlet-mapping>
</web-app>
獲取BServlet 對象當中的 web.xml 標簽
package com.RainbowSea.servlet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
public class BServlet extends GenericServlet {
/**
* 只需要重寫我們常用的 service 父類中的抽象方法即可
*/
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 設置在瀏覽器頁面顯示的格式類型,必須在輸出前設置
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
// 獲取到 GenericServlet 父類適配器當中的 ServletConfig config成員屬性
ServletConfig servletConfig = super.getServletConfig();
// 獲取該Servlet對象的名稱
String ServletName = servletConfig.getServletName();
writer.print(ServletName + "<br>"); // 輸出 Servlet 的對象的名稱
String name = "password"; // 這裡如果我們直接知道name 為 pawword
// 通過 <param-name>user</param-name> 標簽當中的name 值獲取到對應 <param-value>root</param-value> 標簽當中的 value 值
String value = servletConfig.getInitParameter(name);
writer.print(name + "--->" + value); // 頁面輸出
}
}
獲取AServlet 對象當中的 web.xml 標簽
package com.RainbowSea.servlet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
public class AServlet extends GenericServlet {
/**
* 只需要重寫我們常用的 service 父類中的抽象方法即可
*/
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 設置在瀏覽器頁面顯示的格式類型,必須在輸出前設置
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
// 獲取到 GenericServlet 父類適配器當中的 ServletConfig config成員屬性
ServletConfig servletConfig = super.getServletConfig();
// 獲取該Servlet對象的名稱
String ServletName = servletConfig.getServletName();
writer.print(ServletName + "<br>"); // 輸出 Servlet 的對象的名稱
// 一次性獲取到該Servlet對象<init-param>標簽當中配置信息的 name 值
Enumeration<String> names = servletConfig.getInitParameterNames();
while(names.hasMoreElements()) { // 判斷是否還有 元素,有返回 true,沒有返回false。和集合當中的迭代器類似
String name = names.nextElement(); // 取出name 值
// 通過 <param-name>user</param-name> 標簽當中的name 值獲取到對應 <param-value>root</param-value> 標簽當中的 value 值
String value = servletConfig.getInitParameter(name);
writer.print(name + "--->" + value); // 頁面輸出
writer.print("<br>"); // 頁面換行
}
}
}
3.1.3 獲取到 ServletContext對象
方式一: 通過先獲取到 ServletConfig 對象,再通過 ServletConfig 對象獲取到 ServletContext 對象
使用 ServletConfig 當中的 public ServletContext getServletContext(); // 獲取到ServletContext對象
package com.RainbowSea.servlet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
public class BServlet extends GenericServlet {
/**
* 只需要重寫我們常用的 service 父類中的抽象方法即可
*/
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 獲取到 GenericServlet父類適配器當中的 private transient ServletConfig config; 成員屬性
ServletConfig servletConfig = super.getServletConfig();
// 獲取到 servletContext 對象
ServletContext servletContext = servletConfig.getServletContext();
System.out.println("servletContext的值: " + servletContext);
}
}
方式二: 直接通過 父類 GenericServlet 適配器當中的。this,super.getServletContext() 方法 也可以獲取到ServletContext對象
package com.RainbowSea.servlet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
public class BServlet extends GenericServlet {
/**
* 只需要重寫我們常用的 service 父類中的抽象方法即可
*/
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 獲取到GenericServlet父類適配器當中的 servletContext 對象
ServletContext servletContext = super.getServletContext();
System.out.println("servletContext的值: " + servletContext);
}
}
4. ServletContext
- ServletContext 是介面,是Servlet規範中的一員。Tomcat伺服器(WEB伺服器) 實現了ServletContext 介面。
- ServletContext 是一個介面,Tomcat 伺服器對 ServletContext 介面進行了實現。ServletContext 對象的創建也是 Tomcat 伺服器來完成的。啟動webapp的時候創建的。
- ServletContext 對象在伺服器啟動階段創建的,在伺服器關閉的時候銷毀。這就是 ServletContext 對象的生命周期。
- ServletContext 對象的環境對象,(Servlet對象的上下文對象)。
重點:
一個ServletContext 表示的就是一個 webapp 當中的 web.xml
文件當中下配置信息。而一個 webapp 一般只有一個 web.xml 文件。所以只要在同一個 webapp 當中,只要在同一個應用當中,所以的Servlet 對象都是共用同一個 ServletContext對象的 。舉例:Tomcat 伺服器中有一個 webapps ,這個 webapps 下存放了多個 webapp 。假設有 100 個 webapp ,那麼就會有 100 個 ServeltContext 對象,因為一個 webapp 對應一個 web.xml 文件。總之,一個應用,一個 webapp 只有一個 web.xml文件,則只有一個 ServletContext 對象,所有該應用下/webapp下的 Servlet 對象共用。
如下:我們一個 webapp 下有兩個 Servlet 對象,分別為 BServlet 和 AServlet 對象,但是他們兩個的同時在同一個 webapp下的,只有一個 web.xml 文件。所以這個兩個 Servlet 對象共用 一個 ServletContext 對象:
package com.RainbowSea.servlet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class BServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 設置在瀏覽器頁面顯示的格式類型,必須在輸出前設置
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
// 獲取到GenericServlet父類適配器當中的 servletContext 對象
ServletContext servletContext = super.getServletContext();
writer.print("BServlet 下的 servletContext的值: " + servletContext);
}
}
package com.RainbowSea.servlet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
public class AServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 設置在瀏覽器頁面顯示的格式類型,必須在輸出前設置
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
// 獲取到GenericServlet父類適配器當中的 servletContext 對象
ServletContext servletContext = super.getServletContext();
writer.print("AServlet 下的 servletContext的值: " + servletContext);
}
}
ServletContext對應顯示生活中的什麼例子呢?
一個教室里有多個學生,那麼每一個學生就是一個Servlet,這些學生都在同一個教室當中,那麼我們可以把這個教室叫做ServletContext對象。那麼也就是說放在這個ServletContext對象(環境)當中的數據,在同一個教室當中,物品都是共用的。比如:教室中有一個空調,所有的學生都可以操作。可見,空調是共用的。因為空調放在教室當中。教室就是ServletContext對象。
4.1 ServletContext 獲取web.xml 配置文件當中的 <context-param>標簽當中的配置信息
我們想要獲取到web.xml 配置文件大當中的<context-param>標簽當中編寫的的配置信息,需要使用到如下兩個方法:
public String getInitParameter(String name); // 通過初始化參數的name獲取value
public Enumeration<String> getInitParameterNames(); // 獲取所有的初始化參數的name
<!--以上兩個方法是ServletContext對象的方法,這個方法獲取的是什麼信息?是以下的配置信息-->
<context-param>
<param-name>pageSize</param-name>
<param-value>10</param-value>
</context-param>
<context-param>
<param-name>startIndex</param-name>
<param-value>0</param-value>
</context-param>
<!--註意:以上的配置信息屬於應用級的配置信息,一般一個項目中共用的配置信息會放到以上的標簽當中。-->
<!--如果你的配置信息只是想給某一個servlet作為參考,那麼你配置到servlet標簽當中即可,使用ServletConfig對象來獲取。-->
舉例:
如下是 Servle 對象通過,ServletContext對象的上述兩個方法,獲取到
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--如下是: context-param 的編寫的配置信息,該配置信息,被所有的Servlet共用
可以使用ServletContext對象獲取到 -->
<context-param>
<param-name>pageSize</param-name>
<param-value>10</param-value>
</context-param>
<context-param>
<param-name>startIndex</param-name>
<param-value>0</param-value>
</context-param>
<servlet>
<servlet-name>AServlet</servlet-name>
<servlet-class>com.RainbowSea.servlet.AServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AServlet</servlet-name>
<url-pattern>/A</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>BServlet</servlet-name>
<servlet-class>com.RainbowSea.servlet.BServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>BServlet</servlet-name>
<url-pattern>/B</url-pattern>
</servlet-mapping>
</web-app>
package com.RainbowSea.servlet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
public class AServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 設置在瀏覽器頁面顯示的格式類型,必須在輸出前設置
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
// 獲取到GenericServlet父類適配器當中的 servletContext 對象
ServletContext servletContext = super.getServletContext();
writer.print("AServlet 下的 servletContext的值: " + servletContext + "<br>");
// 一次性獲取到 <context-param> </context-param>標簽當中所有的 name 值
Enumeration<String> names = servletContext.getInitParameterNames();
while(names.hasMoreElements()) { // 判斷該集合是否還有元素,有返回true,沒有返回false
// 獲取到元素當中的 name值
String name = names.nextElement();
// 通過對象 <context-param> </context-param>標簽下的name獲取到對應的value值
String value = servletContext.getInitParameter(name);
writer.print(name + "--->" + value);
writer.print("<br>");
}
}
}
package com.RainbowSea.servlet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import java