本篇將主要集中在fitler的以下幾個知識點:
干嘛的
怎么用
多個Filter執(zhí)行的先后順序
注意事項
Filter稱之為過濾器,是用來做一些攔截的任務(wù), 在Servlet接受請求之前,做一些事情,如果不滿足限定,可以拒絕進(jìn)入Servlet
從上面的圖,可以看出一個Filter的工作流程:
一個http請求過來之后
首先進(jìn)入filter,執(zhí)行相關(guān)業(yè)務(wù)邏輯
若判定通行,則進(jìn)入Servlet邏輯,Servlet執(zhí)行完畢之后,又返回Filter,最后在返回給請求方
判定失敗,直接返回,不需要將請求發(fā)給Servlet
通過上面的流程,可以推算使用場景:
在filter層,來獲取用戶的身份
可以考慮在filter層做一些常規(guī)的校驗(如參數(shù)校驗,referer校驗等)
可以在filter層做穩(wěn)定性相關(guān)的工作(如全鏈路打點,可以在filter層分配一個traceId;也可以在這一層做限流等)
1. 基本使用姿勢
要使用一個Filter,一半需要兩步,實現(xiàn)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();
}
主要就三個方法,從命名來看,也比較清晰,在創(chuàng)建Filter對象的時候,調(diào)用 init 方法,銷毀Filter對象的時候,調(diào)用 destroy 方法,當(dāng)請求過來之后,調(diào)用 doFilter,也就是主要的業(yè)務(wù)邏輯所在了
詳細(xì)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的配置規(guī)則沒什么兩樣)
我們的實例,就拿大名鼎鼎的CharacterEncodingFilter來說明,順帶膜拜下Spring的大神的優(yōu)秀源碼
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 執(zhí)行完成,又返回filter");
}
}
上面的實現(xiàn)比較簡單,主要將視線集中在 doFilterInternal 方法內(nèi)部,如果要設(shè)置編碼參數(shù),則直接修改 HttpServletRequest, HttpServletResponse 兩個參數(shù),操作完成之后,執(zhí)行下面這一行
filterChain.doFilter(request, response);
注意
上面這一行執(zhí)行,表示Filter層已經(jīng)通過了,請求可以轉(zhuǎn)發(fā)給下一個Filter或者直接傳給Servlet,而下一個Filter, Servlet執(zhí)行完成之后,還會繼續(xù)往下走,就是上面的那一行輸出,也會被調(diào)用(那一行是我加的,源碼中沒有),所以,如果你不希望繼續(xù)往下走,那么就簡單了,不執(zhí)行上面的那一行即可
問題一:看了上面的源碼,一個很明顯的問題就是,參數(shù)怎么設(shè)置的?
仔細(xì)看上面的源碼,發(fā)現(xiàn)自定義Filter是繼承 org.springframework.web.filter.OncePerRequestFilter 而不是直接實現(xiàn)的 Filter 接口,而且方法內(nèi)也沒有顯示的實現(xiàn) init()方法,所有很容易猜到是父類中實現(xiàn)了參數(shù)的初始化過程
具體的實現(xiàn)邏輯是在 org.springframework.web.filter.GenericFilterBean#init 中,同樣是Spring實現(xiàn)的,主要代碼撈出來
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中配置的參數(shù),然后填充到Filter對象中(對Srping而言,CharacterEncodingFilter就是一個bean),這個具體的邏輯和本篇關(guān)系不大,就直接跳過了
問題二:在Filter層中可以獲取參數(shù)么
從doFilter的方法簽名中看,既然有Request參數(shù),那么應(yīng)該是可以獲取到請求參數(shù)的,那么實際驗證一下
先實現(xiàn)一個最最最簡單的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中獲取參數(shù)時,最好不要直接使用獲取請求流的方式,如果獲取請求流,那么Servlet就獲取不到請求參數(shù)了
問題三:多個filter的順序怎么定
前面學(xué)習(xí)Servlet的時候,也有這個問題,一個URL被多個Servlet命中了,那么先后順序是怎樣的呢?
精確匹配 > 最長匹配 > 其他模糊匹配 > 沒有匹配的則是404
那么Filter呢,他們的區(qū)別還是比較明顯的,很多Filter都是攔截所有的請求,即很多Filter的命中規(guī)則都是一樣的,那么怎么辦?
先執(zhí)行帶有url-pattern標(biāo)簽的filter,再執(zhí)行帶有servlet-name標(biāo)簽的filter
如果同為url-pattern或servlet-name,則會按照在web.xml中的聲明順序執(zhí)行
測試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>
輸出結(jié)果
in TestFilter
in ATestFilter
in ServletFilter
小結(jié)
Filter 通常用于JavaWeb的過濾使用,通過doFilter方法中執(zhí)行
chain.doFilter(request, response);,進(jìn)入下一個Filter或者Servlet執(zhí)行邏輯,當(dāng)執(zhí)行完成之后,依然會回到Filter這一層,繼續(xù)走下去
針對上面的邏輯,F(xiàn)ilter的常見應(yīng)用場景有:
用戶信息獲取,身份校驗
安全校驗(referer校驗失敗,直接拒絕)
穩(wěn)定性相關(guān)(限流,監(jiān)控埋點,全鏈路日志埋點)
url-mapping 的優(yōu)先執(zhí)行,其次是 servlet-mapping
同一個匹配方式(如都是url-mapping)中,根據(jù)在xml中定義的先后順序來確定
正常業(yè)務(wù),請記得一定執(zhí)行 chain.doFilter(request, response), 最后把它放在finnal塊中,防止你在Filter中的代碼拋異常導(dǎo)致進(jìn)入不到后續(xù)的邏輯
在Filter中不要直接獲取請求數(shù)據(jù)流(請求流被讀取完之后,Servlet就get不到了!)