驚呆了,Servlet Filter和Spring MVC Interceptor的實現居然這麼簡單

来源:https://www.cnblogs.com/erlie/archive/2020/04/20/12741097.html
-Advertisement-
Play Games

前言 創建型:單例模式,工廠模式,建造者模式,原型模式 結構型:橋接模式,代理模式,裝飾器模式,適配器模式,門面模式,組合模式,享元模式 行為型:觀察者模式,模板模式,策略模式,責任鏈模式,狀態模式,迭代器模式,訪問者模式 介紹 在工作中,我們經常要和Servlet Filter,Spring MV ...


前言

創建型:單例模式,工廠模式,建造者模式,原型模式
結構型:橋接模式,代理模式,裝飾器模式,適配器模式,門面模式,組合模式,享元模式
行為型:觀察者模式,模板模式,策略模式,責任鏈模式,狀態模式,迭代器模式,訪問者模式

介紹

在工作中,我們經常要和Servlet Filter,Spring MVC Interceptor打交道,雖然我配置寫的很6,但是出了問題還得到處google,於是看了一下源碼,用Demo的方式來分析一下這兩者是怎麼工作的。

Servlet Filter

Filter的使用

可能很多小伙伴沒怎麼用過Filter,我就簡單演示一下

1.在web.xml中配置2個Filter

<filter-mapping>
    <filter-name>logFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>imageFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

2.實現如下,略去了init方法和destroy方法

@WebFilter(filterName = "logFilter")
public class LogFilter implements Filter {

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("LogFilter execute");
        chain.doFilter(request, response);
    }
}
@WebFilter(filterName = "imageFilter")
public class ImageFilter implements Filter {

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("ImageFilter execute");
        chain.doFilter(request, response);
    }
}

3.然後你訪問任意一個servlet方法,LogFilter和ImageFilter的doFilter方法都會執行

如果你在一個Filter方法後不加chain.doFilter(request, response)
則後續的Filter和Servlet都不會執行,這是為什麼呢?看完我手寫的Demo你一下就明白了

可以看到Filter可以在請求到達Servlet之前做處理,如

  1. 請求編碼
  2. 敏感詞過濾等

有興趣的小伙伴可以看看相關的源碼

手寫Filter的實現

Servlet介面,任何一個web請求都會調用service方法

public interface Servlet {
    public void service();
}
public class MyServlet implements Servlet {
    @Override
    public void service() {
        System.out.println("MyServlet的service方法執行了");
    }
}

攔截器介面

public interface Filter {
    public void doFilter(FilterChain chain);
}
public class LogFilter implements Filter {
    @Override
    public void doFilter(FilterChain chain) {
        System.out.println("LogFilter執行了");
        chain.doFilter();
    }
}
public class ImageFilter implements Filter {
    @Override
    public void doFilter(FilterChain chain) {
        System.out.println("ImageFilter執行了");
        chain.doFilter();
    }
}

攔截器鏈對象

public interface FilterChain {
    public void doFilter();
}
public class ApplicationFilterChain implements FilterChain {

    private Filter[] filters = new Filter[10];
    private Servlet servlet = null;

    // 總共的Filter數目
    private int n;

    // 當前執行完的filter數目
    private int pos;

    @Override
    public void doFilter() {
        if (pos < n) {
            Filter curFilter = filters[pos++];
            curFilter.doFilter(this);
            return;
        }
        servlet.service();
    }

    public void addFilter(Filter filter) {
        // 這裡源碼有動態擴容的過程,和ArrayList差不多
        // 我就不演示了,直接賦數組大小為10了
        filters[n++] = filter;
    }

    public void setServlet(Servlet servlet) {
        this.servlet = servlet;
    }
}

測試例子

public class Main {

    public static void main(String[] args) {
        // 在tomcat源碼中,會將一個請求封裝為一個ApplicationFilterChain對象
        // 然後執行ApplicationFilterChain的doFilter方法
        ApplicationFilterChain applicationFilterChain = new ApplicationFilterChain();
        applicationFilterChain.addFilter(new LogFilter());
        applicationFilterChain.addFilter(new ImageFilter());
        applicationFilterChain.setServlet(new MyServlet());

        // LogFilter執行了
        // ImageFilter執行了
        // MyServlet的service方法執行了
        applicationFilterChain.doFilter();
    }
}

如果任意一個Filter方法的最後不加上chain.doFilter(),則後面的攔截器及Servlet都不會執行了。相信你看完ApplicationFilterChain類的doFilter方法一下就明白了,就是一個簡單的遞歸調用

Spring MVC Interceptor

Interceptor的使用

以前寫過一篇攔截器應用的文章,有想瞭解使用方式的小伙伴可以看一下

用Spring MVC攔截器做好web應用的安保措施

今天就來分析一下攔截器是怎麼實現的?可以通過以下方式實現攔截器

  1. 實現HandlerInterceptor介面
  2. 繼承HandlerInterceptorAdapter抽象類,按需重寫部分實現即可,(HandlerInterceptorAdapter也實現了HandlerInterceptor介面)

總而言之攔截器必須必須實現了HandlerInterceptor介面

HandlerInterceptor有如下3個方法

boolean preHandler():在controller執行之前調用
void postHandler():controller執行之後,且頁面渲染之前調用
void afterCompletion():頁面渲染之後調用,一般用於資源清理操作

在這裡插入圖片描述
這個圖應該很好的顯示了一個請求可以被攔截的地方

  1. Servlet Filter是對一個請求到達Servlet的過程進行攔截
  2. 而HandlerInterceptor是當請求到達DispatcherServlet後,在Controller的方法執行前後進行攔截

手寫Interceptor的實現

我來手寫一個Demo,你一下就能明白了

攔截介面,為了方便我這裡就只定義了一個方法

public interface HandlerInterceptor {
    boolean preHandle();
}

定義如下2個攔截器

public class CostInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle() {
        // 這裡可以針對傳入的參數做一系列事情,我這裡就簡單返回true了;
        System.out.println("CostInterceptor 執行了");
        return true;
    }
}
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle() {
        System.out.println("LoginInterceptor 執行了");
        return true;
    }
}

存放攔截器的容器

public class HandlerExecutionChain {

    private List<HandlerInterceptor> interceptorList = new ArrayList<>();

    public void addInterceptor(HandlerInterceptor interceptor) {
        interceptorList.add(interceptor);
    }

    public boolean applyPreHandle() {
        for (int i = 0; i < interceptorList.size(); i++) {
            HandlerInterceptor interceptor = interceptorList.get(i);
            if (!interceptor.preHandle()) {
                return false;
            }
        }
        return true;
    }
}

演示DispatcherServlet的調用過程

public class Main {

    public static void main(String[] args) {
        // Spring MVC會根據請求返回一個HandlerExecutionChain對象
        // 然後執行HandlerExecutionChain的applyPreHandle方法,controller中的方法
        HandlerExecutionChain chain = new HandlerExecutionChain();
        chain.addInterceptor(new CostInterceptor());
        chain.addInterceptor(new LoginInterceptor());

        // 只有攔截器都返回true,才會調用controller的方法
        // CostInterceptor 執行了
        // LoginInterceptor 執行了
        if (!chain.applyPreHandle()) {
            return;
        }
        result();
    }

    public static void result() {
        System.out.println("這是controller的方法");
    }
}

如果任意一個Interceptor返回false,則後續的Interceptor和Controller中的方法都不會執行原因在Demo中顯而易見

當想對請求增加新的過濾邏輯時,只需要定義一個攔截器即可,完全符合開閉原則。

不知道你意識到沒有Servlet Filter和Spring MVC Interceptor都是用責任鏈模式實現的

來看看DispatcherServlet是怎麼做的?和我們上面寫的demo一模一樣

我們用servlet寫web應用時,一個請求地址寫一個Servlet類。
而用了spring mvc後,整個應用程式只有一個Servlet即DispatcherServlet,所有的請求都發送到DispatcherServlet,然後通過方法調用的方式執行controller的方法

DispatcherServlet的doDispatch方法源碼如下,省略了一部分邏輯(所有的請求都會執行這個方法)

protected void doDispatch() {

	// 執行所有HandlerInterceptor的preHandle方法
	if (!mappedHandler.applyPreHandle(processedRequest, response)) {
		return;
	}

	// 執行controller中的方法
	mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

	// 執行所有HandlerInterceptor的postHandle方法
	mappedHandler.applyPostHandle(processedRequest, response, mv);
}

Interceptor可以有如下用處

  1. 記錄介面響應時間
  2. 判斷用戶是否登陸
  3. 許可權校驗等

可以看到Servlet Filter和Spring MVC Interceptor都能對請求進行攔截,只不過時機不同。並且Servlet Filter是Servlet的規範,而Spring MVC Interceptor只能在Spring MVC中使用

歡迎關註

在這裡插入圖片描述

參考博客

[0]https://mp.weixin.qq.com/s/8AIRvz5HOgjw12PbsjZhCQ
[1]https://www.cnblogs.com/xrq730/p/10633761.html
filter源碼分析
[2]https://cloud.tencent.com/developer/article/1129724
[3]https://www.jianshu.com/p/be47c9d89175


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • max-http-header-size設置 server.max-http-header-size=999999999 //953m JVM參數配置 -Xms800m -Xmx800m 寫一個rest api @RestController("action") public class HttpH ...
  • 版權申明:本文為博主窗戶(Colin Cai)原創,歡迎轉帖。如要轉貼,必須註明原文網址 http://www.cnblogs.com/Colin-Cai/p/12741423.html 作者:窗戶 QQ/微信:6679072 E-mail:[email protected] 有的時候,我們在寫Pytho ...
  • 在讀取txt文件時遇到IndexError: list index out of range如下圖: 加入如下代碼,檢查原因: 已經知道rows一共有20662行,往下翻結果,還是報錯了: 意味著在讀取rows的第9411行時出錯了,這意味在9411可能存在list[index]的index超出範圍 ...
  • 題目:用*號輸出字母C的圖案。 程式分析:可先用'*'號在紙上寫出字母C,再分行輸出。 程式源代碼: 1 #include "stdio.h" 2 int main() 3 { 4 printf("用 * 號輸出字母 C!\n"); 5 printf(" ****\n"); 6 printf(" * ...
  • datetime.date()方法 用法:targetDay = datetime.date(year, month, day),傳入年月日,返回一個date類型的時間 date類型的對象的方法 1. targetDay.year # 返回targetDay年份 2. targetDay.month ...
  • C++的核心理念之一是RAII,Resource Acquisition Is Initialization,資源獲取即初始化。資源有很多種,記憶體、互斥鎖、文件、套接字等;RAII可以用來實現一種與作用域綁定的資源管理方法(如 );這些都不在本文的討論範圍之內。 記憶體是一種資源。從字面上來看,“資源 ...
  • @2020-4-20 作業: 1、編寫遠程執行命令的CS架構軟體 # 服務端 # _*_coding:utf-8_*_ __author__ = 'cc' from socket import * import subprocess ip_port = ('127.0.0.1', 1080) buf ...
  • Java初學者有必要來一張JAVA知識結構圖,首先知道總體有哪些知識分類,進而繼續更細化各個知識。 來一起細品吧! ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...