實(shí)例
實(shí)現(xiàn)一個(gè)簡(jiǎn)單的過濾器只需要兩步
1巨朦,實(shí)現(xiàn)Filter接口寫一個(gè)過濾器實(shí)現(xiàn)類
public class DemoFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("[DemoFilter-before]doFilter");
chain.doFilter(request, response);
System.out.println("[DemoFilter-after]doFilter");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("[DemoFilter-before]init");
}
@Override
public void destroy() {
System.out.println("[DemoFilter-before]destroy");
}
}
2盏触,web.xml文件中新增相關(guān)filter配置
<filter>
<filter-name>DemoFilter</filter-name>
<filter-class>com.ryan.springtest.filter.DemoFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>DemoFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
輸出
此時(shí)啟動(dòng)tomcat訪問任一url玩徊,即可看到相應(yīng)的輸出信息
[DemoFilter-before]doFilter
[DemoFilter-after]doFilter
注:filterChain為過濾器鏈谍失,表示執(zhí)行完這個(gè)過濾器之后接著執(zhí)行下一個(gè)過濾器
原理
過濾器的具體實(shí)現(xiàn)依賴于容器晰筛,本文的源碼分析是基于Tomcat的實(shí)現(xiàn)嫡丙。
要完成過濾器的實(shí)現(xiàn),Tomcat首先需要加載我們定義的過濾器传惠,接著針對(duì)每一次請(qǐng)求找到對(duì)應(yīng)的過濾器迄沫,最后是執(zhí)行過濾器中的doFilter,觸發(fā)過濾器鏈的執(zhí)行卦方,下面將按照這個(gè)邏輯對(duì)源碼進(jìn)行簡(jiǎn)單的分析羊瘩。
過濾器加載
過濾器的加載是在Tomcat啟動(dòng)的時(shí)候完成的,Tomcat啟動(dòng)的時(shí)候盼砍,會(huì)加載web.xml中的配置信息尘吗,filter的加載具體是在ContextConfig類的configureContext方法中,關(guān)鍵代碼如下
for (FilterDef filter : webxml.getFilters().values()) {
if (filter.getAsyncSupported() == null) {
filter.setAsyncSupported("false");
}
context.addFilterDef(filter);
}
for (FilterMap filterMap : webxml.getFilterMappings()) {
context.addFilterMap(filterMap);
}
此時(shí)分別加載filter和filterMap相關(guān)信息浇坐,并保存在上下文環(huán)境中
加載完相關(guān)配置信息后睬捶,還需對(duì)具體的filter進(jìn)行初始化,這一步在StandardContext類的startInternal方法中完成近刘,關(guān)鍵代碼如下
if (ok) {
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
}
public boolean filterStart() {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Starting filters");
}
// Instantiate and record a FilterConfig for each defined filter
boolean ok = true;
synchronized (filterConfigs) {
filterConfigs.clear();
for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
String name = entry.getKey();
if (getLogger().isDebugEnabled()) {
getLogger().debug(" Starting filter '" + name + "'");
}
try {
ApplicationFilterConfig filterConfig =
new ApplicationFilterConfig(this, entry.getValue());
filterConfigs.put(name, filterConfig);
} catch (Throwable t) {
t = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(t);
getLogger().error(sm.getString(
"standardContext.filterStart", name), t);
ok = false;
}
}
}
return ok;
}
遍歷剛剛從web.xml解析出來的filter配置信息擒贸,并調(diào)用ApplicationFilterConfig構(gòu)造方法進(jìn)行初始化,保存在filterConfigs中并存到上下文環(huán)境中觉渴。
過濾器鏈生成
當(dāng)請(qǐng)求進(jìn)入tomcat的時(shí)候介劫,會(huì)被匹配的過濾器過濾,多個(gè)匹配的過濾器組成一個(gè)過濾器鏈案淋,并按照我們?cè)趙eb.xml中定義的filter-mapping的順序執(zhí)行座韵。
被tomcat處理的請(qǐng)求,最終會(huì)被StandardWrapperValve類的invoke方法處理,對(duì)應(yīng)的過濾器鏈也是在此時(shí)生成的誉碴,關(guān)鍵代碼如下
ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
@SuppressWarnings("deprecation")
public static ApplicationFilterChain createFilterChain (ServletRequest request, Wrapper wrapper, Servlet servlet) {
ApplicationFilterChain filterChain = null;
if (request instanceof Request) {
//略
} else {
// Request dispatcher in use
filterChain = new ApplicationFilterChain();
}
filterChain.setServlet(servlet);
filterChain.setSupport(((StandardWrapper)wrapper).getInstanceSupport());
// Acquire the filter mappings for this Context
StandardContext context = (StandardContext) wrapper.getParent();
FilterMap filterMaps[] = context.findFilterMaps();
// If there are no filter mappings, we are done
if ((filterMaps == null) || (filterMaps.length == 0))
return (filterChain);
// Acquire the information we will need to match filter mappings
String servletName = wrapper.getName();
// Add the relevant path-mapped filters to this filter chain
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersURL(filterMaps[i], requestPath))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
//略
} else {
filterChain.addFilter(filterConfig);
}
}
// Add filters that match on servlet name second
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersServlet(filterMaps[i], servletName))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
//略
} else {
filterChain.addFilter(filterConfig);
}
}
// Return the completed filter chain
return (filterChain);
}
上述的代碼比較長(zhǎng)宦棺,主要的邏輯有
- 每個(gè)請(qǐng)求需要生成對(duì)應(yīng)的ApplicationFilterChain,invoke方法中調(diào)用ApplicationFilterFactory類的createFilterChain方法創(chuàng)建一個(gè)ApplicationFilterChain黔帕,其中包含了目標(biāo)servlet以及對(duì)應(yīng)的過濾器鏈代咸。
- createFilterChain方法中,首先設(shè)置了目標(biāo)servlet成黄,
filterChain.setServlet(servlet);
侣背。 - 接著從上下文環(huán)境中取出之前解析的filterMaps信息,
FilterMap filterMaps[] = context.findFilterMaps();
慨默。 - 遍歷filterMaps,判斷當(dāng)前的請(qǐng)求是否符合攔截條件弧腥,若符合則將filterConfig放進(jìn)filterChain中厦取,從這里可以看出,實(shí)際決定過濾器執(zhí)行順序的是filter-mapping在web.xml中的配置順序管搪。
至此一個(gè)ApplicationFilterChain便構(gòu)建好了虾攻,包含一個(gè)目標(biāo)servlet和我們想要的過濾器鏈。
過濾器鏈執(zhí)行
獲取到過濾器鏈之后更鲁,接下來就是過濾器鏈的具體執(zhí)行霎箍,回到上一步分析開始的StandardWrapperValve類的invoke方法中,現(xiàn)在我們拿到的ApplicationFilterChain澡为,便可以繼續(xù)向下分析了漂坏。
try {
if ((servlet != null) && (filterChain != null)) {
// Swallow output if needed
if (context.getSwallowOutput()) {
try {
SystemLogHandler.startCapture();
if (request.isAsyncDispatching()) {
request.getAsyncContextInternal().doInternalDispatch();
} else if (comet) {
filterChain.doFilterEvent(request.getEvent());
} else {
filterChain.doFilter(request.getRequest(), response.getResponse());
}
} finally {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
context.getLogger().info(log);
}
}
} else {
if (request.isAsyncDispatching()) {
request.getAsyncContextInternal().doInternalDispatch();
} else if (comet) {
filterChain.doFilterEvent(request.getEvent());
} else {
filterChain.doFilter(request.getRequest(), response.getResponse());
}
}
}
//略
上述代碼中,我們關(guān)注的是filterChain.doFilter
方法媒至,在這里將會(huì)觸發(fā)過濾器鏈的執(zhí)行顶别,繼續(xù)跟蹤源碼
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if( Globals.IS_SECURITY_ENABLED ) {
//略
} else {
internalDoFilter(request,response);
}
}
最終實(shí)際的處理方法是internalDoFilter
private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = null;
try {
filter = filterConfig.getFilter();
support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT, filter, request, response);
//略
if( Globals.IS_SECURITY_ENABLED ) {
//略
} else {
filter.doFilter(request, response, this);
}
support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT, filter, request, response);
} catch (IOException | ServletException | RuntimeException e) {
//略
} catch (Throwable e) {
//略
}
return;
}
// We fell off the end of the chain -- call the servlet instance
try {
//略
if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)) {
if( Globals.IS_SECURITY_ENABLED ) {
//略
} else {
servlet.service(request, response);
}
} else {
servlet.service(request, response);
}
support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
servlet, request, response);
} catch (IOException e) {
//略
} finally {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(null);
lastServicedResponse.set(null);
}
}
}
上面只列出我們關(guān)注的關(guān)鍵代碼
-
ApplicationFilterConfig filterConfig = filters[pos++];
此處取出當(dāng)前要執(zhí)行的filter,并把pos加1拒啰。 - 執(zhí)行
filter.doFilter
方法驯绎,并將當(dāng)前的filterChain傳入過濾器中。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("[DemoFilter-before]doFilter");
chain.doFilter(request, response);
System.out.println("[DemoFilter-after]doFilter");
}
- 上面是我們定義的filter谋旦,當(dāng)我們調(diào)用chain.doFilter的時(shí)候剩失,最終又回到上面的internalDoFilter方法中,取出過濾器鏈中的下一個(gè)過濾器進(jìn)行執(zhí)行册着。
- 當(dāng)過濾器鏈執(zhí)行完成后拴孤,便會(huì)執(zhí)行servlet.service方法。
- 最后internalDoFilter執(zhí)行完成后指蚜,便會(huì)回到上一個(gè)過濾器的doFilter中乞巧,繼續(xù)執(zhí)行chain.doFilter之后的代碼,直到執(zhí)行完所有匹配的過濾器摊鸡。
至此绽媒,過濾器鏈的執(zhí)行便完成了蚕冬。
過濾器關(guān)鍵類與接口
- Filter:實(shí)現(xiàn)一個(gè)過濾器可以實(shí)現(xiàn)該接口
- ContextConfig:加載web.xml中的配置信息,并保存到上下文環(huán)境中
- StandardContext:對(duì)具體的filter進(jìn)行初始化是辕,并保存到上下文環(huán)境中
- StandardWrapperValve:將請(qǐng)求映射到ApplicationFilterChain囤热,并負(fù)責(zé)過濾器的執(zhí)行。
- ApplicationFilterChain:負(fù)責(zé)過濾器鏈的遞歸調(diào)用
過濾器應(yīng)用示例
編碼設(shè)置:設(shè)置請(qǐng)求及相應(yīng)的編碼
日志記錄:記錄請(qǐng)求信息的日志获三,以便進(jìn)行信息監(jiān)控旁蔼、信息統(tǒng)計(jì)、計(jì)算PV(Page View)等疙教。
權(quán)限檢查:如登錄檢測(cè)棺聊,進(jìn)入處理器檢測(cè)檢測(cè)是否登錄,如果沒有直接返回到登錄頁面贞谓。
通用行為:讀取cookie得到用戶信息并將用戶對(duì)象放入請(qǐng)求限佩,從而方便后續(xù)流程使用。