驚呆了,Servlet Filter和Spring MVC Interceptor的實現居然這麼簡單
前言
建立型:單例模式,工廠模式,建造者模式,原型模式
結構型:橋接模式,代理模式,裝飾器模式,介面卡模式,門面模式,組合模式,享元模式
行為型:觀察者模式,模板模式,策略模式,責任鏈模式,狀態模式,迭代器模式,訪問者模式
介紹
在工作中,我們經常要和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之前做處理,如
- 請求編碼
- 敏感詞過濾等
有興趣的小夥伴可以看看相關的原始碼
手寫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應用的安保措施
今天就來分析一下攔截器是怎麼實現的?可以通過以下方式實現攔截器
- 實現HandlerInterceptor介面
- 繼承HandlerInterceptorAdapter抽象類,按需重寫部分實現即可,(HandlerInterceptorAdapter也實現了HandlerInterceptor介面)
總而言之攔截器必須必須實現了HandlerInterceptor介面
HandlerInterceptor有如下3個方法
boolean preHandler():在controller執行之前呼叫
void postHandler():controller執行之後,且頁面渲染之前呼叫
void afterCompletion():頁面渲染之後呼叫,一般用於資源清理操作
這個圖應該很好的顯示了一個請求可以被攔截的地方
- Servlet Filter是對一個請求到達Servlet的過程進行攔截
- 而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可以有如下用處
- 記錄介面響應時間
- 判斷使用者是否登陸
- 許可權校驗等
可以看到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/be47c9d89