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

Java識堂發表於2020-04-20

前言

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

介紹

在工作中,我們經常要和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

相關文章