JavaWeb之Servlet、攔截器、監聽器及程式設計思想

淵渟嶽發表於2022-03-21

本文包含的內容有:

  1. Servlet的理解
  2. 自定義Servlet、監聽器和過濾器
  3. 三者的一點點程式設計設計思想
  4. 後續的學習

JavaWeb是Web開發的重要基礎,對Servlet、監聽器和過濾器等知識的掌握程度,將會影響到你後面學習SpringWeb框架難易程度。

先了解下我們在學習的東西是幹嘛的

B/S模式

B端=瀏覽器端,可以看作是通用標準的客戶端,所有瀏覽器都基於通用標準去開發的客戶端軟體;

S端=伺服器端,就是我們開發的web服務,在B/S端中我們只需要開發服務端,區別於C/S模式,在C/S模式中我們客戶端app和服務端服務都需要我們自己開發完成。

簡單理解下瀏覽器到伺服器的過程:

  1. 瀏覽器請求發出http://www.xxxx.com/path1/ser/
  2. 通過DNS域名解析找到IP地址
  3. 通過IP地址找到部署服務的伺服器
  4. 通過埠找到伺服器中具體的服務程式
  5. 因為一個Tomcat(web伺服器)可以部署多個專案,所以往往喜歡使用埠後的第一個路徑表示要訪問的服務(path1路徑)
  6. 接著就是路徑ser,ser表示要訪問的servlet,一個專案必然有數不清的servlet入口,分別用於實現不同功能的小程式服務。

那為啥叫小程式服務呢?有小就有大的咯,哪大的是誰?是Tomcat伺服器咯。可以看作一個Tomcat中有多個獨立提供不同功能的小程式服務--servlet。

到此引出了為什麼要有servlet,為不同請求獨立提供不同的服務

學習之前記得配好開發環境:兩個都要配置

  1. JAVA_HOME配置:C:\Program Files\Java\jdk1.8.0_181
  2. 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的作用:接收使用者請求、提供服務處理入口、響應處理結果。

image

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的物件從被建立到被銷燬的過程。

  1. 由Tomcat伺服器啟動時建立Servlet;
  2. Servlet的配置資訊ServletConfig作為引數傳遞到void init(ServletConfig var1)方法中;
  3. 在開發Servlet時,將init方法中的ServletConfig物件賦值給成員變數(放大作用域),再通過getServletConfig() 獲取ServletConfig物件;這件事已經由抽象類GenericServlet做了,所以一般自定義Servlet都是繼承GenericServlet類或其子類來實現;
  4. 每個Servlet請求的入口為service()方法,每次請求都會從這裡進去;
  5. 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

image

分析結果:

自定義了一個名稱為:MyFirstServlet的Servlet,但列印結果是Servlet容器有三個Servlet。原因是在Tomcat中的web.xml中配置了另外兩個Servlet

image

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

image

找到心目中的物件,開啟連結檢視你的物件

image

看不懂全英文的就用瀏覽器翻譯外掛翻譯,比如谷歌瀏覽器自帶的翻譯功能。

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,為了方便理解的簡圖

image

第一個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 控制檯列印結果:

image

第2次訪問/myfirstservlet 控制檯列印結果:

image

第1次訪問/mysecondservlet 控制檯列印結果:

image

第2次訪問/mysecondservlet 控制檯列印結果:

image

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

image

案例三:自定義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.簡單總結

先後順序簡圖

image

第一次訪問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案例程式碼全家福

image

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()】使用了責任鏈模式,不同過濾器負責不同的過濾功能,也就是擔任著不同的職責,都肩負著不同的責任,將各施其職的完成一整套可插拔的過濾功能。責任鏈模式只需要將請求傳送到責任鏈上即可,無須關心請求的處理細節和請求的傳遞過程,請求會自動進行傳遞,所以責任鏈將請求的傳送者和請求的處理者解耦了。

後續學習

  1. SpringWeb框架學習

    DispatcherServlet本質是個Servlet,功能是分配任務,簡稱分配器小程式服務;除了分配器,SpringWeb還擴充套件了,攔截器,處理器,控制器,檢視解析器等等功能,為java Web開發提供更全面更高效的Web框架。

  2. HTTP協議的學習

  3. Tomcat 伺服器的學習

下載原始碼學習:https://pan.baidu.com/s/1GfZgO7tX6d-lL3jUeAsT7w

防失效--關注WX公眾號【Java全棧佈道師】回覆獲取提取碼:mywebdemo

image

Java往期文章

Java全棧學習路線、學習資源和麵試題一條龍

我心裡優秀架構師是怎樣的?

免費下載經典程式設計書籍

image

相關文章