本文包含的內容有:
Servlet的理解 自定義Servlet、監聽器和過濾器 三者的一點點程式設計設計思想 後續的學習
JavaWeb是Web開發的重要基礎,對Servlet、監聽器和過濾器等知識的掌握程度,將會影響到你後面學習SpringWeb框架難易程度。
先了解下我們在學習的東西是幹嘛的
B/S模式
B端=瀏覽器端,可以看作是通用標準的客戶端,所有瀏覽器都基於通用標準去開發的客戶端軟體;
S端=伺服器端,就是我們開發的web服務,在B/S端中我們只需要開發服務端,區別於C/S模式,在C/S模式中我們客戶端app和服務端服務都需要我們自己開發完成。
簡單理解下瀏覽器到伺服器的過程:
瀏覽器請求發出http://www.xxxx.com/path1/ser/ 通過DNS域名解析找到IP地址 通過IP地址找到部署服務的伺服器 通過埠找到伺服器中具體的服務程式 因為一個Tomcat(web伺服器)可以部署多個專案,所以往往喜歡使用埠後的第一個路徑表示要訪問的服務(path1路徑) 接著就是路徑ser,ser表示要訪問的servlet,一個專案必然有數不清的servlet入口,分別用於實現不同功能的小程式服務。
那為啥叫小程式服務呢?有小就有大的咯,哪大的是誰?是Tomcat伺服器咯。可以看作一個Tomcat中有多個獨立提供不同功能的小程式服務--servlet。
到此引出了為什麼要有servlet,為不同請求獨立提供不同的服務。
學習之前記得配好開發環境:兩個都要配置
JAVA_HOME配置:C:\Program Files\Java\jdk1.8.0_181 CLASS_PATH配置:.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar
1.Servlet
1.1.簡介
Servlet(Server applet),全稱Java Servlet,直接意思是Java的小程式服務,是執行在 Web 伺服器或應用伺服器上的程式。Servlet一般只用來擴充套件HTTP協議的Web服務。
Servlet的作用:接收使用者請求、提供服務處理入口、響應處理結果。
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生命週期指Servlet的物件從被建立到被銷燬的過程。
由Tomcat伺服器啟動時建立Servlet; Servlet的配置資訊ServletConfig作為引數傳遞到void init(ServletConfig var1)方法中; 在開發Servlet時,將init方法中的ServletConfig物件賦值給成員變數(放大作用域),再通過getServletConfig() 獲取ServletConfig物件;這件事已經由抽象類GenericServlet做了,所以一般自定義Servlet都是繼承GenericServlet類或其子類來實現; 每個Servlet請求的入口為service()方法,每次請求都會從這裡進去; Servlet物件銷燬前(WEB專案解除安裝時)呼叫destroy(),用來做一些收尾工作,釋放資源。
關於getServletInfo的方法,再GenericServlet中實現結果時返回空字串。
兩個關鍵點
init()方法:每個servlet都只會在第1次請求時執行init初始化方法;
service()方法
每次請求進來都會呼叫service()方法; Servlet是以單例項多執行緒方式工作,它是以多執行緒的方式呼叫service()方法; Servlet不是執行緒安全,所以儘量不要在service()方法中操作全域性變數,若非要呼叫全域性變數務必使用關鍵字volatile修飾,並通過synchronized修飾的方法來操作全域性變數。
通過Servlet介面原始碼可知,ServletConfig介面、ServletContext介面、ServletRequest介面和ServletResponse介面最為重要。
1.2.ServletConfig
Servlet的配置資訊,每一個Servlet都有其唯一對應的ServletConfig。
建立:由Tomcat伺服器啟動時建立
獲取:ServletConfig物件作為引數傳遞到init()方法中,可以在init()方法直接獲取。
抽象類GenericServlet已經實現Servlet和ServletConfig介面,所以一般自定義Servlet都是繼承GenericServlet類或其子類來實現,可以直接呼叫ServletConfig介面中的4個方法來獲取servlet配置資訊。
ServletConfig 介面原始碼
public interface ServletConfig {
String getServletName();
ServletContext getServletContext();
String getInitParameter(String var1);
Enumeration<String> getInitParameterNames();
}
ServletConfig介面除了ServletName和初始化引數資訊,最重要的就是ServletContext物件的獲取了。
1.3.ServletContext
一個Web應用對應一個唯一的ServletContext物件(可以在tomcat的server.xml中的Context標籤檢視)
ServletContext物件在專案啟動時建立,並在初始化時提供ServletConfig物件來訪問ServletContext,在專案停止或過載配置時銷燬。
獲取:通過ServletConfig的getServletContext()方法獲取。
功能:
1、可以獲取整個WEB應用的初始化引數
<context-param>
<param-name>myContextName</param-name>
<param-value>xxxxxxvalue</param-value>
</context-param>
2、可以獲取資源的真實路徑(物理路徑),主要在檔案的上傳和下載時使用。
3 、可以作為一個域物件在不同的web資源之間共享資料。
1.4.Servlet案例
public class MyFirstServlet implements Servlet{
private ServletConfig servletConfig;
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("==Servlet1==MyFirstServlet--init");
this.servletConfig = config; //將初始化傳進來的Servlet配置物件儲存起來
// 獲取Servlet初始化時的所有初始化引數名稱
Enumeration<String> initParameterNames = this.servletConfig.getInitParameterNames();
// 遍歷所有初始化 引數名稱 和 引數值
while(initParameterNames.hasMoreElements()) {
String nextElement = initParameterNames.nextElement();
String initParameter = this.servletConfig.getInitParameter(nextElement);
System.out.println("[param-name="+ nextElement + ",param-value=" + initParameter +"]");
}
}
@Override
public ServletConfig getServletConfig() {
return this.servletConfig; // 提供獲取ServletConfig方法
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
System.out.println("==Servlet1==MyFirstServlet--service");
// 檢視容器中有多少個Servlet
ServletContext servletContext = getServletConfig().getServletContext();
Map<String, ? extends ServletRegistration> servletRegistrations = servletContext.getServletRegistrations();
Iterator<? extends Map.Entry<String, ? extends ServletRegistration>> iterator = servletRegistrations.entrySet().iterator();
while(iterator.hasNext()) {
Entry<String, ? extends ServletRegistration> next = iterator.next();
System.out.println("key="+next.getKey()+",value="+next.getValue());
}
// 響應瀏覽器
res.getWriter().write("200");
}
@Override
public void destroy() {
System.out.println("==Servlet1==MyFirstServlet--destroy");
}
@Override
public String getServletInfo() {
return "";
}
}
web.xml 配置
<servlet>
<servlet-name>MyFirstServlet</servlet-name>
<servlet-class>com.yty.servlet.MyFirstServlet</servlet-class>
<init-param>
<param-name>initParam</param-name>
<param-value>asdfsdaf</param-value>
</init-param></servlet>
<servlet-mapping>
<servlet-name>MyFirstServlet</servlet-name>
<url-pattern>/myfirstservlet</url-pattern>
</servlet-mapping>
啟動Tomcat服務
瀏覽器訪問:http://localhost:8080/mywebdemo/myfirstservlet
執行結果:Servlet容器有三個Servlet
分析結果:
自定義了一個名稱為:MyFirstServlet的Servlet,但列印結果是Servlet容器有三個Servlet。原因是在Tomcat中的web.xml中配置了另外兩個Servlet
Tomcat中的兩個預設Servlet配置:DefaultServlet和JspServlet
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
<!-- The mapping for the default servlet -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- The mappings for the JSP servlet -->
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>
到這裡,應該知道了為什麼你的jsp動態頁面可以在瀏覽器訪問了吧。
在springboot中的Spring-webmvc預設是不支援jsp的,這兩個Servlet也是沒有的,預設只有一個dispatcherServlet,可通過上面的方式自行驗證。
1.5.GenericServlet 抽象類
前面的Servlet案例是直接實現Servlet介面來實現,在案例中很容易看出有很多程式碼都是通用的,比如:儲存初始化時傳進來的Servlet配置物件。為了簡化Servlet的開發就有了GenericServlet 抽象類,它實現了除service方法以外的所有方法。繼承GenericServlet 只需要實現service方法即可建立一個Servlet。
除此之外GenericServlet 抽象類還實現了ServletConfig介面,也就是說我們可以通過GenericServlet 直接獲取servlet配置相關資訊。
1.6.HttpServlet 抽象類
HttpServlet 抽象類只繼承了GenericServlet 抽象類,由上一點知識可以知道,HttpServlet 具備了Servlet功能和獲取Servlet配置資訊功能。如果說GenericServlet 簡化了Servlet的開發,那麼HttpServlet 更大程度簡化了HTTP協議方面的Servlet開發。
HttpServlet 抽象類面向的就是HTTP協議的Servlet開發。如果我們要實現一個HTTP協議的Servlet開發,那麼就直接繼承HttpServlet抽象類來實現。HttpServlet抽象類中沒有抽象方法,只需要按需實現doGet、doPost、doPut、doDelete等等方法即可,這些doXXX方法本質還是呼叫service方法。
1.7.關於其他介面和類
比如ServletRequest、ServletResponse、HttpServletRequest、HttpServletResponse等等介面和類,沒辦法一個個的講完,還是要自己主動學習。很多類和介面就像字典一樣,需要的是怎麼學會查字典,而不是背字典,所以來學習怎麼“查字典”吧。
以Tomcat 8.5.XX為例:
先開啟ServletApi字典:https://tomcat.apache.org/tomcat-8.5-doc/servletapi/allclasses.html
再Ctrl + F,輸入HttpServletRequest
找到心目中的物件,開啟連結檢視你的物件
看不懂全英文的就用瀏覽器翻譯外掛翻譯,比如谷歌瀏覽器自帶的翻譯功能。
2.過濾器--Filter
2.1.簡介
過濾器,顧名思義就是用來過濾的,就像家裡用來過濾水用的過濾器一樣,起到過濾的作用。過濾器只能對進入伺服器時對請求和響應物件進行過濾,請求響應的過程是不管的。
水的過濾可以分為很多不同層次的處理,比如:先過濾大顆粒泥沙、再過濾微小的顆粒、再深層過濾、最後殺菌消毒。這種有先後的一層一層的過濾,又稱為過濾鏈。程式設計是現實的對映,所以同樣也有過濾鏈,就像過濾水一樣。
為什麼要有過濾器呢?在Servlet入口方法service中處理不就可以咯?
過濾器的作用
過濾請求:在客戶端的請求訪問servlet之前進行過濾。 過濾響應:在servlet的響應傳送回客戶端之前進行過濾。
過濾器生命週期
伺服器啟動時建立一個例項執行init方法,過濾器常駐記憶體中,直到過濾器被移除或伺服器關閉才會執行destroy方法
應用場景
統一設定編碼格式 身份驗證 訪問許可權控制 日誌記錄 敏感資料過濾 防止XSS攻擊的過濾 ……
過濾器的實現跟Servlet類似,都需要直接或間接實現介面。
過濾器介面原始碼
public interface Filter {
default void init(FilterConfig filterConfig) throws ServletException {
}
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
default void destroy() {
}
}
看原始碼可以知道,Filter介面的設計和Servlet很相似。
特點
服務啟動時就進行初始化,初始化時呼叫init方法,傳入FilterConfig物件。開發時,將FilterConfig儲存起來,方便後續獲取Filter的配置資訊。 配了過濾器的Servlet,每次接收請求都會通過過濾處理入口:doFilter方法;doFilter方法前做請求統一處理,doFilter方法後做響應統一處理。
2.2.相關介面和類
FilterConfig介面:過濾器的配置資訊(名稱,初始化引數,Servlet容器物件);
GenericFilter抽象類:對Filter做了通用實現,繼承它可以更簡單的實現自定義過濾器;
HttpFilter抽象類:繼承GenericFilter,面向HTTP協議的過濾器再封裝。
這些類的設計都和Servlet的相似,可參考1.6.談到的API文件 + 對比Servlet來學習。
2.3.兩個Servlet+過濾鏈案例
通過這個案例來學習Servlet和Filter,為了方便理解的簡圖
第一個Servlet程式碼
public class MyFirstServlet implements Servlet{
private ServletConfig servletConfig;
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("==Servlet1==MyFirstServlet--init");
this.servletConfig = config; //將初始化傳進來的Servlet配置物件儲存起來
// 獲取Servlet初始化時的所有初始化引數名稱
Enumeration<String> initParameterNames = this.servletConfig.getInitParameterNames();
// 遍歷所有初始化 引數名稱 和 引數值
while(initParameterNames.hasMoreElements()) {
String nextElement = initParameterNames.nextElement();
String initParameter = this.servletConfig.getInitParameter(nextElement);
System.out.println("==Servlet1==MyFirstServlet--[param-name="+ nextElement + ",param-value=" + initParameter +"]");
}
}
@Override
public ServletConfig getServletConfig() {
return this.servletConfig; // 提供獲取ServletConfig方法
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
System.out.println("==Servlet1==MyFirstServlet--service");
// 檢視容器中有多少個Servlet
ServletContext servletContext = getServletConfig().getServletContext();
Map<String, ? extends ServletRegistration> servletRegistrations = servletContext.getServletRegistrations();
Iterator<? extends Map.Entry<String, ? extends ServletRegistration>> iterator = servletRegistrations.entrySet().iterator();
while(iterator.hasNext()) {
Entry<String, ? extends ServletRegistration> next = iterator.next();
System.out.println("==Servlet1==MyFirstServlet--[key="+next.getKey()+",value="+next.getValue()+"]");
}
// 響應瀏覽器
((HttpServletResponse)res).setStatus(200);
res.getWriter().write("MyFirstServlet-200");
}
@Override
public void destroy() {
System.out.println("==Servlet1==MyFirstServlet--destroy");
}
@Override
public String getServletInfo() {
return "";
}
}
第二個Servlet程式碼
public class MySecondServlet implements Servlet{
private ServletConfig servletConfig;
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("==Servlet2==MySecondServlet--init");
this.servletConfig = config; //將初始化傳進來的Servlet配置物件儲存起來
}
@Override
public ServletConfig getServletConfig() {
return this.servletConfig; // 提供獲取ServletConfig方法
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
System.out.println("==Servlet2==MySecondServlet--service");
// 檢視容器中有多少個Servlet
ServletContext servletContext = getServletConfig().getServletContext();
Map<String, ? extends ServletRegistration> servletRegistrations = servletContext.getServletRegistrations();
Iterator<? extends Map.Entry<String, ? extends ServletRegistration>> iterator = servletRegistrations.entrySet().iterator();
while(iterator.hasNext()) {
Entry<String, ? extends ServletRegistration> next = iterator.next();
System.out.println("==Servlet2==MySecondServlet--[key="+next.getKey()+",value="+next.getValue()+"]");
}
// 響應瀏覽器
((HttpServletResponse)res).setStatus(200);// 設定響應碼
res.getWriter().write("MySecondServlet-200");//列印到瀏覽器頁面
}
@Override
public void destroy() {
System.out.println("==Servlet2==MySecondServlet--destroy");
}
@Override
public String getServletInfo() {
return "";
}
}
第一個過濾器程式碼:起到統一編碼的功能
public class MyFirstFilter implements Filter {
// 統一設定編碼格式
private String encoding;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String filterName = filterConfig.getFilterName();
this.encoding= filterConfig.getInitParameter("Encoding");
System.out.println("==Filter1==MyFirstFilter--"+filterName+"---init---charset引數值="+encoding);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("==Filter1==MyFirstFilter--doFilter");
/*
* 請求統一過濾處理
*/
request.setCharacterEncoding(encoding);
/*
* 把過濾器接入過濾鏈
*/
chain.doFilter(request, response);
/*
* 響應統一過濾處理
*/
response.setCharacterEncoding(encoding);
int status = ((HttpServletResponse)response).getStatus();
System.out.println("==Filter1==MyFirstFilter--響應碼:"+status);
}
@Override
public void destroy() {
System.out.println("==Filter1==MyFirstFilter--destroy");
}
}
第二個Filter程式碼
public class MySecondFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String filterName = filterConfig.getFilterName();
System.out.println("==Filter2==MySecondFilter--"+filterName+"---init");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("==Filter2==MySecondFilter--doFilter---");
/*
* 請求統一過濾處理
*/
System.out.println("==Filter2==MySecondFilter--請求統一過濾處理");
/*
* 把過濾器接入過濾鏈
*/
chain.doFilter(request, response);
/*
* 響應統一過濾處理
*/
int status = ((HttpServletResponse)response).getStatus();
System.out.println("==Filter2==MySecondFilter--響應碼:"+status);
}
@Override
public void destroy() {
System.out.println("==Filter2==MySecondFilter--destroy");
}
}
web.xml 配置
<filter>
<filter-name>MyFirstFilter</filter-name>
<filter-class>com.yty.filter.MyFirstFilter</filter-class>
<init-param>
<param-name>Encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter>
<filter-name>MySecondFilter</filter-name>
<filter-class>com.yty.filter.MySecondFilter</filter-class>
</filter>
<!-- filter-mapping配置順序==過濾順序 -->
<filter-mapping>
<filter-name>MyFirstFilter</filter-name>
<url-pattern>/myfirstservlet</url-pattern><!-- 過濾的/myfirstservlet路徑 -->
</filter-mapping>
<filter-mapping>
<filter-name>MySecondFilter</filter-name>
<url-pattern>/*</url-pattern><!-- 過濾本專案的所有路徑 -->
</filter-mapping>
<!-- 第一個Servlet -->
<servlet>
<servlet-name>MyFirstServlet</servlet-name>
<servlet-class>com.yty.servlet.MyFirstServlet</servlet-class>
<init-param>
<param-name>initParam</param-name>
<param-value>asdfsdaf</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>MyFirstServlet</servlet-name>
<url-pattern>/myfirstservlet</url-pattern>
</servlet-mapping>
<!-- 第二個Servlet -->
<servlet>
<servlet-name>MySecondServlet</servlet-name>
<servlet-class>com.yty.servlet.MySecondServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MySecondServlet</servlet-name>
<url-pattern>/mysecondservlet</url-pattern>
</servlet-mapping>
執行結果:
服務啟動階段:
==Filter1==MyFirstFilter--MyFirstFilter---init---charset引數值=utf-8 ==Filter2==MySecondFilter--MySecondFilter---init
第1次訪問/myfirstservlet 控制檯列印結果:
第2次訪問/myfirstservlet 控制檯列印結果:
第1次訪問/mysecondservlet 控制檯列印結果:
第2次訪問/mysecondservlet 控制檯列印結果:
3.監聽器--Listener
3.1.簡介
監聽器,顧名思義就是監聽某樣東西,當它監聽的某件事情發生時就觸發監聽器去處理。比如,手機一直監聽著手機電量,當低個於你設定的百分比(如10%電量)時,就會彈一個框提示你:電量過低了,及時充電。
按監聽的物件分類
ServletContext物件監聽器:ServletContext物件建立和銷燬的監聽器 HttpSession物件監聽器:HttpSession物件建立和銷燬的監聽器 ServletRequest物件監聽器:ServletRequest物件建立和銷燬的監聽器 ……
監聽器的作用
預先設定需要監聽的事物,監聽到事物的變化便做出相應的處理
生命週期
伺服器啟動時建立,直到監聽器被移除或伺服器關閉才會執行銷燬
應用場景
容器啟動時一層層的觸發事物,有條不絮的一層層的啟動 監聽請求和響應 監聽會話資訊 ……
以ServletContextListener為例:當Web應用程式的Servlet上下文更改時,底層事件源會釋出一個帶有ServletContextEvent事件的變更通知給ServletContextListener.contextInitialized方法,讓ServletContextListener監聽器去處理容器啟動完之後的事情。
3.2.Listener 案例
案例一:自定義ServletContextListener
public class MyServletContextListener implements ServletContextListener{
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("=======MyServletContextListener--contextInitialized");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("=======MyServletContextListener--contextDestroyed");
}
}
web.xml 配置
<listener>
<listener-class>com.yty.listener.MyServletContextListener</listener-class>
</listener>
執行結果:
容器啟動完就會列印:
=======MyServletContextListener--contextInitialized
容器銷燬就會列印:
=======MyServletContextListener--contextDestroyed
在此只知道監聽事件和監聽器,關於事件源則不需要開發者去關注,只要知道是Servlet容器啟動完成後便會觸發。
案例二:自定義HttpSessionListener
public class MyHttpSessionListener implements HttpSessionListener {
private int count=0;//記錄session的數量
@Override
public void sessionCreated(HttpSessionEvent se) {
count++;
se.getSession().setAttribute("Count", count);
System.out.println("==MyHttpSessionListener==sessionCreated--"+se.getSession().getId());
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
count--;
se.getSession().setAttribute("Count", count);
System.out.println("==MyHttpSessionListener==sessionDestroyed--"+se.getSession().getId());
}
}
在任意Servlet中加入:觸發HttpSession事件
// 獲取Session
HttpServletRequest hreq = (HttpServletRequest)req;
Integer attribute = (Integer)hreq.getSession().getAttribute("Count");// 獲取session數量,觸發HttpSession事件
System.out.println("=====session 數量:["+attribute +"]");
web.xml配置
<listener>
<listener-class>com.yty.listener.MyHttpSessionListener</listener-class>
</listener>
<!--預設的會話超時時間間隔,以分鐘為單位 -->
<session-config>
<session-timeout>1</session-timeout>
</session-config>
執行結果:
訪問帶有操作session的Servlet時觸發HttpSessionListener,從而為HTTP無狀態協議附上sessionID,通過ID來繫結訪問使用者,實現有狀態會話(HTTP+Session場景的有狀態會話)。
控制檯列印:
==MyHttpSessionListener==sessionCreated--3E08CB886E5838A84756FE08123D7A82 =====session 數量:[1]
在瀏覽器F12中可以看到瀏覽器接收到的響應頭帶有Set-Cookie:JSESSIONID=控制檯列印的ID
案例三:自定義ServletRequestListener
public class MyServletRequestListener implements ServletRequestListener{
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("==MyServletRequestListener---requestInitialized--"+sre.getServletRequest().getRemoteHost());
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("==MyServletRequestListener---requestDestroyed--"+sre.getServletRequest().getRemoteHost());
}
}
web.xml 配置
<listener>
<listener-class>com.yty.listener.MyServletRequestListener</listener-class>
</listener>
執行結果:
接收到請求開始觸發
==MyServletRequestListener---requestInitialized--0:0:0:0:0:0:0:1
響應請求觸發
==MyServletRequestListener---requestDestroyed--0:0:0:0:0:0:0:1
3.3.簡單總結
先後順序簡圖
第一次訪問Servlet到容器銷燬的細節順序
=======MyServletContextListener--contextInitialized
==Filter1==MyFirstFilter--MyFirstFilter---init---charset引數值=utf-8
==Filter2==MySecondFilter--MySecondFilter---init
==MyServletRequestListener---requestInitialized--0:0:0:0:0:0:0:1
==Servlet1==MyFirstServlet--init ==Servlet1
==MyFirstServlet--[param-name=initParam,param-value=asdfsdaf]
==Filter1==MyFirstFilter--doFilter
==Filter2==MySecondFilter--doFilter---
==Filter2==MySecondFilter--請求統一過濾處理
==Servlet1==MyFirstServlet--service
==Servlet1==MyFirstServlet--[key=MySecondServlet,value=org.apache.catalina.core.ApplicationServletRegistration@4582c182]
==Servlet1==MyFirstServlet--[key=default,value=org.apache.catalina.core.ApplicationServletRegistration@1316c8af]
==Servlet1==MyFirstServlet--[key=jsp,value=org.apache.catalina.core.ApplicationServletRegistration@31e644bd]
==Servlet1==MyFirstServlet--[key=MyFirstServlet,value=org.apache.catalina.core.ApplicationServletRegistration@243bc5a2]
==MyHttpSessionListener==sessionCreated--3E08CB886E5838A84756FE08123D7A82
=====session 數量:[1]
==Filter2==MySecondFilter--響應碼:200
==Filter1==MyFirstFilter--響應碼:200
==MyServletRequestListener---requestDestroyed--0:0:0:0:0:0:0:1 三月 21, 2022 7:52:30 上午 org.apache.catalina.startup.HostConfig reload 資訊: 重新載入上下文[/mywebdemo]
==Servlet1==MyFirstServlet--destroy 三月 21, 2022 7:52:30 上午 org.apache.catalina.core.StandardContext reload 資訊: 已開始重新載入名為[/mywebdemo]的上下文
==Filter1==MyFirstFilter--destroy
==Filter2==MySecondFilter--destroy
=======MyServletContextListener--contextDestroyed
更多細節可以下載原始碼學習:下載連結已經附在文末
Servlet、Filter和Listener的共同特點:在自定義中都可以獲取到ServletContext,對Servlet容器在不同階段進行定製化實現。
來個Servlet、Filter和Listener案例程式碼全家福
JavaWeb的一點點程式設計設計思想
單一職責原則
以Servlet的方式分離不同請求,不同職責的Servlet處理不同的請求;不同Filter分別實現不同的過濾功能;不同的Listener分別處理不同的監聽事件。
單例項多執行緒工作方式
Servlet就是以單例項多執行緒的方式工作,每個請求在一個獨立的執行緒中執行,而提供服務的Servlet例項只有一個。每個請求相關的資料都是用Servlet子類的service方法(或者是doGet或doPost方法)的引數傳入的。只要Servlet中的程式碼只使用區域性變數,Servlet就不會導致同步問題。若非要呼叫全域性變數務必使用關鍵字volatile修飾,並通過synchronized修飾的方法來操作全域性變數,根據業務需求儘可能做到執行緒安全。
裝飾模式
提供 ServletRequest 介面的便捷實現,希望將請求適應 Servlet 的開發人員可以將其子類化。此類實現 Wrapper 或 Decorator 模式,方法預設呼叫包裝的請求物件。
觀察者模式
通過呼叫XXXListener類來觀察Web中的物件變化情況,如:觀察ServletContext、Servlet、ServletRequest、ServletResponse和HttpSession等等物件。當事件源檢測到發生預定的事件(Event),便呼叫監聽器(Listener)方法來觸發監聽事件。
責任鏈模式
過濾鏈【chain.doFilter()】使用了責任鏈模式,不同過濾器負責不同的過濾功能,也就是擔任著不同的職責,都肩負著不同的責任,將各施其職的完成一整套可插拔的過濾功能。責任鏈模式只需要將請求傳送到責任鏈上即可,無須關心請求的處理細節和請求的傳遞過程,請求會自動進行傳遞,所以責任鏈將請求的傳送者和請求的處理者解耦了。
後續學習
SpringWeb框架學習
DispatcherServlet本質是個Servlet,功能是分配任務,簡稱分配器小程式服務;除了分配器,SpringWeb還擴充套件了,攔截器,處理器,控制器,檢視解析器等等功能,為java Web開發提供更全面更高效的Web框架。
HTTP協議的學習
Tomcat 伺服器的學習
下載原始碼學習:https://pan.baidu.com/s/1GfZgO7tX6d-lL3jUeAsT7w
防失效--關注WX公眾號【Java全棧佈道師】回覆獲取提取碼:mywebdemo