本篇將主要集中在fitler的以下幾個知識點:
干嘛的
怎么用
多個Filter執行的先后順序
注意事項
Filter稱之為過濾器,是用來做一些攔截的任務, 在Servlet接受請求之前,做一些事情,如果不滿足限定,可以拒絕進入Servlet
從上面的圖,可以看出一個Filter的工作流程:
一個http請求過來之后
首先進入filter,執行相關業務邏輯
若判定通行,則進入Servlet邏輯,Servlet執行完畢之后,又返回Filter,最后在返回給請求方
判定失敗,直接返回,不需要將請求發給Servlet
通過上面的流程,可以推算使用場景:
在filter層,來獲取用戶的身份
可以考慮在filter層做一些常規的校驗(如參數校驗,referer校驗等)
可以在filter層做穩定性相關的工作(如全鏈路打點,可以在filter層分配一個traceId;也可以在這一層做限流等)
1. 基本使用姿勢
要使用一個Filter,一半需要兩步,實現Filter接口的自定義類,web.xml中對filter的定義
public interface Filter {
public void init(FilterConfig filterConfig) throws ServletException;
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException;
public void destroy();
}
主要就三個方法,從命名來看,也比較清晰,在創建Filter對象的時候,調用 init 方法,銷毀Filter對象的時候,調用 destroy 方法,當請求過來之后,調用 doFilter,也就是主要的業務邏輯所在了
詳細case后面再說
接下來就是xml的配置了,和Servlet類似,每自定義一個,都需要在xml中加上一個配置(挺繁瑣的操作的)
<!-- 解決亂碼的問題 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<async-supported>true</async-supported>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
配置也比較簡單了,一個 一個 前者定義具體的Filter,后者表示這個Filter攔截的URL (看起來和Servlet的配置規則沒什么兩樣)
我們的實例,就拿大名鼎鼎的CharacterEncodingFilter來說明,順帶膜拜下Spring的大神的優秀源碼
public class CharacterEncodingFilter extends OncePerRequestFilter {
private String encoding;
private boolean forceEncoding = false;
public CharacterEncodingFilter() {
}
public CharacterEncodingFilter(String encoding) {
this(encoding, false);
}
public CharacterEncodingFilter(String encoding, boolean forceEncoding) {
Assert.hasLength(encoding, "Encoding must not be empty");
this.encoding = encoding;
this.forceEncoding = forceEncoding;
}
public void setEncoding(String encoding) {
this.encoding = encoding;
}
public void setForceEncoding(boolean forceEncoding) {
this.forceEncoding = forceEncoding;
}
@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null)) {
request.setCharacterEncoding(this.encoding);
if (this.forceEncoding) {
response.setCharacterEncoding(this.encoding);
}
}
filterChain.doFilter(request, response);
System.out.printl("servelt 執行完成,又返回filter");
}
}
上面的實現比較簡單,主要將視線集中在 doFilterInternal 方法內部,如果要設置編碼參數,則直接修改 HttpServletRequest, HttpServletResponse 兩個參數,操作完成之后,執行下面這一行
filterChain.doFilter(request, response);
注意
上面這一行執行,表示Filter層已經通過了,請求可以轉發給下一個Filter或者直接傳給Servlet,而下一個Filter, Servlet執行完成之后,還會繼續往下走,就是上面的那一行輸出,也會被調用(那一行是我加的,源碼中沒有),所以,如果你不希望繼續往下走,那么就簡單了,不執行上面的那一行即可
問題一:看了上面的源碼,一個很明顯的問題就是,參數怎么設置的?
仔細看上面的源碼,發現自定義Filter是繼承 org.springframework.web.filter.OncePerRequestFilter 而不是直接實現的 Filter 接口,而且方法內也沒有顯示的實現 init()方法,所有很容易猜到是父類中實現了參數的初始化過程
具體的實現邏輯是在 org.springframework.web.filter.GenericFilterBean#init 中,同樣是Spring實現的,主要代碼撈出來
public final void init(FilterConfig filterConfig) throws ServletException {
Assert.notNull(filterConfig, "FilterConfig must not be null");
if (logger.isDebugEnabled()) {
logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'");
}
this.filterConfig = filterConfig;
// Set bean properties from init parameters.
try {
PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
String msg = "Failed to set bean properties on filter '" +
filterConfig.getFilterName() + "': " + ex.getMessage();
logger.error(msg, ex);
throw new NestedServletException(msg, ex);
}
// Let subclasses do whatever initialization they like.
initFilterBean();
if (logger.isDebugEnabled()) {
logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully");
}
}
看上面一大串的代碼,到底干了嘛? 簡單來講,就是獲取xml中配置的參數,然后填充到Filter對象中(對Srping而言,CharacterEncodingFilter就是一個bean),這個具體的邏輯和本篇關系不大,就直接跳過了
問題二:在Filter層中可以獲取參數么
從doFilter的方法簽名中看,既然有Request參數,那么應該是可以獲取到請求參數的,那么實際驗證一下
先實現一個最最最簡單的Filter
public class TestFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("in filter");
System.out.println("args: " + JSON.toJSONString(request.getParameterMap()));
chain.doFilter(request, response);
System.out.println("out filter");
}
@Override
public void destroy() {
}
}
輸出如下
in filter
args: {"name":["Hello"],"password":["world"]}
out filter
在Filter中獲取參數時,最好不要直接使用獲取請求流的方式,如果獲取請求流,那么Servlet就獲取不到請求參數了
問題三:多個filter的順序怎么定
前面學習Servlet的時候,也有這個問題,一個URL被多個Servlet命中了,那么先后順序是怎樣的呢?
精確匹配 > 最長匹配 > 其他模糊匹配 > 沒有匹配的則是404
那么Filter呢,他們的區別還是比較明顯的,很多Filter都是攔截所有的請求,即很多Filter的命中規則都是一樣的,那么怎么辦?
先執行帶有url-pattern標簽的filter,再執行帶有servlet-name標簽的filter
如果同為url-pattern或servlet-name,則會按照在web.xml中的聲明順序執行
測試case如下,我們定義三個Filter:
TestFilter: 匹配所有路徑
ATestFilter: 匹配所有路徑
ServletFilter: 匹配 mvc-servlet
// ATestFilter
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("in ATestFilter");
chain.doFilter(request, response);
}
// TestFilter
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("in TestFilter");
chain.doFilter(request, response);
}
// ServletFilter
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("in ServletFilter");
chain.doFilter(request, response);
}
<filter>
<filter-name>servletFilter</filter-name>
<filter-class>com.test.ServletFilter</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>servletFilter</filter-name>
<servlet-name>mvc-dispatcher</servlet-name>
</filter-mapping>
<filter>
<filter-name>testFilter</filter-name>
<filter-class>com.test.TestFilter</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>testFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>atestFilter</filter-name>
<filter-class>com.test.ATestFilter</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>atestFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
輸出結果
in TestFilter
in ATestFilter
in ServletFilter
小結
Filter 通常用于JavaWeb的過濾使用,通過doFilter方法中執行
chain.doFilter(request, response);,進入下一個Filter或者Servlet執行邏輯,當執行完成之后,依然會回到Filter這一層,繼續走下去
針對上面的邏輯,Filter的常見應用場景有:
用戶信息獲取,身份校驗
安全校驗(referer校驗失敗,直接拒絕)
穩定性相關(限流,監控埋點,全鏈路日志埋點)
url-mapping 的優先執行,其次是 servlet-mapping
同一個匹配方式(如都是url-mapping)中,根據在xml中定義的先后順序來確定
正常業務,請記得一定執行 chain.doFilter(request, response), 最后把它放在finnal塊中,防止你在Filter中的代碼拋異常導致進入不到后續的邏輯
在Filter中不要直接獲取請求數據流(請求流被讀取完之后,Servlet就get不到了!)