1.引言
我們?cè)诰帉慾avaweb程序的時(shí)候,時(shí)常會(huì)用filter這個(gè)組件,它能將我們一些通用邏輯抽取出來(lái),在servlet執(zhí)行業(yè)務(wù)邏輯之前運(yùn)行,
達(dá)到簡(jiǎn)化代碼和復(fù)用的目的.比如最常用的場(chǎng)景全站編碼和登錄驗(yàn)證功能.
servlet3.0以前我們只能通過(guò)web.xml的方式配置filter,并且多個(gè)filter的執(zhí)行順序是根據(jù)你web.xml中書寫順序來(lái)決定的.
servlet3.0以后,提供了注解的方式注入filter,只需要在filter類上加上@WebFilter()注解即可,大大的簡(jiǎn)化了開發(fā)復(fù)雜度.
2.拋出問(wèn)題
注解的方式書寫的filter的執(zhí)行順序又是如何的呢?
網(wǎng)上的很多資料都說(shuō)是根據(jù)filter的類名來(lái)決定,也有說(shuō)是根據(jù)filter的注解的name屬性值的字母順序來(lái)決定的.
對(duì)不對(duì)呢?
2.驗(yàn)證問(wèn)題
我們創(chuàng)建了三個(gè)filter 來(lái)驗(yàn)證此問(wèn)題
filter1號(hào)
package com.jk1123.web.filter.demo01;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/*")
public class OrderFilter1 implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
System.out.println("orderFilter1執(zhí)行了..");
chain.doFilter(req, resp);
}
public void init(FilterConfig config) throws ServletException {
}
}filter2號(hào)
package com.jk1123.web.filter.demo02;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/*")
public class OrderFilter2 implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
System.out.println("orderFilter2執(zhí)行了..");
//這是放行
chain.doFilter(req, resp);
}
public void init(FilterConfig config) throws ServletException {
}
}
filter3號(hào)
package com.jk1123.web.filter.demo03;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/*")
public class OrderFilter3 implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
System.out.println("orderFilter3執(zhí)行了..");
//這是放行
chain.doFilter(req, resp);
}
public void init(FilterConfig config) throws ServletException {
}
}
配上一個(gè)servlet 來(lái)訪問(wèn)試試
package com.jk1123.web.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/foo")
public class FooServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().print("foo servlet");
}
}
驗(yàn)證結(jié)果:
3.查看源碼
可以從上面驗(yàn)證看出 好像并不是根據(jù)類名或者filter的name屬性的字母排序執(zhí)行,那到底是根據(jù)什么執(zhí)行的呢?
點(diǎn)開源碼,我們一點(diǎn)點(diǎn)探尋它的秘密。
需要搞清楚如下問(wèn)題
1.filterChain是什么時(shí)候執(zhí)行的呢?
2.filterChain中的filter來(lái)源何處?
3.standardContext什么時(shí)候開始收集的過(guò)濾器集合
我們先查詢第一段源碼 解密filterChain是什么時(shí)候執(zhí)行的
//在org.apache.catalina.core.StandardWrapperValve 類中有如下一個(gè)方法
public final void invoke(Request request, Response response)
throws IOException, ServletException {
//省略掉大段無(wú)關(guān)代碼
//0.生命servlet對(duì)象
Servlet servlet = null;
/**
* 省略一大段無(wú)關(guān)代碼
*/
try {
if (!unavailable) {
//1.這里創(chuàng)建正在訪問(wèn)的servlet對(duì)象
servlet = wrapper.allocate();
}
} catch (UnavailableException e) {
//省略掉大段無(wú)關(guān)代碼
}
//省略大段無(wú)關(guān)代碼
//2.創(chuàng)建過(guò)濾器鏈對(duì)象
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
try {
if ((servlet != null) && (filterChain != null)) {
// Swallow output if needed
if (context.getSwallowOutput()) {
//省略掉大段無(wú)關(guān)代碼
} else {
if (request.isAsyncDispatching()) {
request.getAsyncContextInternal().doInternalDispatch();
} else {
//3.執(zhí)行過(guò)濾鏈對(duì)象doFilter方法
filterChain.doFilter
(request.getRequest(), response.getResponse());
}
}
}
} catch (ClientAbortException | CloseNowException e) {
//省略掉大段無(wú)關(guān)代碼
}
}
我們一會(huì)兒回過(guò)頭來(lái)看它是如何創(chuàng)建過(guò)濾器鏈對(duì)象的代碼,我們先來(lái)看他是如何執(zhí)行過(guò)濾器鏈的,
過(guò)濾器鏈對(duì)象的實(shí)現(xiàn)為:
package org.apache.catalina.core;
//我們只保留 跟執(zhí)行序列有關(guān)的代碼
public final class ApplicationFilterChain implements FilterChain {
//當(dāng)前正在執(zhí)行的filter索引
private int pos = 0;
//總共有多少個(gè)filter匹配上了
private int n = 0;
//關(guān)聯(lián)的要執(zhí)行的servlet對(duì)象
private Servlet servlet = null;
//匹配上的filter數(shù)組
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if( Globals.IS_SECURITY_ENABLED ) {
//刪除無(wú)關(guān)代碼
} else {
//0.執(zhí)行內(nèi)容的doFilter方法
internalDoFilter(request,response);
}
}
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
// Call the next filter if there is one
if (pos < n) {
//這個(gè)地方主義有個(gè)pos++ 進(jìn)來(lái)一次 ++一次
ApplicationFilterConfig filterConfig = filters[pos++];
try {
Filter filter = filterConfig.getFilter();
//刪除大段無(wú)關(guān)代碼
if( Globals.IS_SECURITY_ENABLED ) {
//刪除大段無(wú)關(guān)代碼
} else {
//執(zhí)行過(guò)濾器鏈中的過(guò)濾器的doFilter方法
//而我們的過(guò)濾器中滿足條件后 放行 放行就會(huì)跳轉(zhuǎn)回來(lái)執(zhí)行過(guò)濾器鏈的
//的doFilter 也就是又回來(lái)執(zhí)行第二個(gè)
filter.doFilter(request, response, this);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.filter"), e);
}
return;
}
try {
//刪除大段無(wú)關(guān)代碼
// Use potentially wrapped request from this point
if ((request instanceof HttpServletRequest) &&
(response instanceof HttpServletResponse) &&
Globals.IS_SECURITY_ENABLED ) {
//刪除大段無(wú)關(guān)代碼
} else {
//如果沒(méi)有需要執(zhí)行的filter就會(huì)執(zhí)行 servlet的service方法 也就是我們寫的業(yè)務(wù)邏輯
servlet.service(request, response);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.servlet"), e);
} finally {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(null);
lastServicedResponse.set(null);
}
}
}
}
從filterChain類的源碼可以看出底層是包含了 所匹配上的filter數(shù)組 也就是添加進(jìn)去匹配上過(guò)濾器對(duì)象是有序的 添加的時(shí)候就決定了!!!
那么它是什么時(shí)候添加的呢?
2.filterChain中的filter來(lái)源何處?
其實(shí)我們?cè)谠趏rg.apache.catalina.core.StandardWrapperValve 類的invoke方法中
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);點(diǎn)開這段代碼查詢把!
public static ApplicationFilterChain createFilterChain(ServletRequest request,
Wrapper wrapper, Servlet servlet) {
// If there is no servlet to execute, return null
if (servlet == null)
return null;
// 在這里創(chuàng)建ApplicationFilterChain 對(duì)象
//但是對(duì)象里還沒(méi)有filter對(duì)象
ApplicationFilterChain filterChain = null;
if (request instanceof Request) {
Request req = (Request) request;
if (Globals.IS_SECURITY_ENABLED) {
// Security: Do not recycle
filterChain = new ApplicationFilterChain();
} else {
filterChain = (ApplicationFilterChain) req.getFilterChain();
if (filterChain == null) {
filterChain = new ApplicationFilterChain();
req.setFilterChain(filterChain);
}
}
} else {
// Request dispatcher in use
filterChain = new ApplicationFilterChain();
}
filterChain.setServlet(servlet);
filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());
// Acquire the filter mappings for this Context
StandardContext context = (StandardContext) wrapper.getParent();
//獲取ServletContext對(duì)象 注冊(cè)的所有的filter數(shù)組
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
DispatcherType dispatcher =
(DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);
String requestPath = null;
Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);
if (attribute != null){
requestPath = attribute.toString();
}
String servletName = wrapper.getName();
//這里開始遍歷 filterMaps數(shù)組根據(jù)請(qǐng)求路徑匹配添加
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;
}
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;
}
filterChain.addFilter(filterConfig);
}
// Return the completed filter chain
return filterChain;
}可以看出在創(chuàng)建filterChain對(duì)象時(shí)候,從ServletContext獲取所有注冊(cè)的filter的數(shù)組 取出需要的添加到這次請(qǐng)求創(chuàng)建的filterChain對(duì)象中
而且servletContext對(duì)象的注冊(cè)的所有的過(guò)濾器本身就是一個(gè)數(shù)組 本身就是有序的,所以遍歷匹配的時(shí)候,也就是有序的!
3、ServletContext什么時(shí)候開始收集的數(shù)組,從哪來(lái)的呢?
這個(gè)要從tomcat啟動(dòng)的時(shí)候來(lái)看了!
而StandardContext創(chuàng)建完成以后就要開始初始化操作了!
StandardContext.startInternal()方法---->fireLifecycleEvent()方法-->ContextConfig.lifecycleEvent()-->ContextConfig.lifecycleEventcon-->ContextConfig.con.figureStart()-->ContextConfig.webConfig()
好了我們現(xiàn)在查看該方法:
protected void webConfig() {
WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),
context.getXmlValidation(), context.getXmlBlockExternal());
Set<WebXml> defaults = new HashSet<>();
defaults.add(getDefaultWebXmlFragment(webXmlParser));
//創(chuàng)建了web.xml 配置文件對(duì)象
//也就是它就代表我們項(xiàng)目的配置相關(guān)的信息
WebXml webXml = createWebXml();
// Parse context level web.xml
InputSource contextWebXml = getContextWebXmlSource();
//解析web.xml 配置文件
//發(fā)現(xiàn)配置文件中的 filter servlet listener等配置
//而xml的解析是從上到下 所以你在web.xml 配置filter
//得到filter集合就是有序的
if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
ok = false;
}
ServletContext sContext = context.getServletContext();
//省略大段無(wú)關(guān)代碼
if (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
// Steps 4 & 5.
//掃描編譯的類文件 尋找注解方式書寫的servlet filter listener
processClasses(webXml, orderedFragments);
}
//省略大段無(wú)關(guān)代碼
}
從上面代碼 可以看出web,xml配置的filter肯定是有序的了 解析的時(shí)候 就會(huì)收集到webXml對(duì)象的
//采用的是linkedHashset來(lái)存儲(chǔ)的 是有序的
private final Set<FilterMap> filterMaps = new LinkedHashSet<>();解析xml過(guò)程我們就不看了 人家才是的digester的xml解析框架來(lái)做.
我們來(lái)查看processClasses(webXml, orderedFragments); 這個(gè)方法是解析注解用的
protected void processClasses(WebXml webXml, Set<WebXml> orderedFragments) {
// Step 4. Process /WEB-INF/classes for annotations and
// @HandlesTypes matches
Map<String, JavaClassCacheEntry> javaClassCache = new HashMap<>();
if (ok) {
//獲取項(xiàng)目下的類路徑
WebResource[] webResources =
context.getResources().listResources("/WEB-INF/classes");
for (WebResource webResource : webResources) {
// Skip the META-INF directory from any JARs that have been
// expanded in to WEB-INF/classes (sometimes IDEs do this).
if ("META-INF".equals(webResource.getName())) {
continue;
}
//開始根據(jù)注解解析了
processAnnotationsWebResource(webResource, webXml,
webXml.isMetadataComplete(), javaClassCache);
}
}
// Step 5. Process JARs for annotations and
// @HandlesTypes matches - only need to process those fragments we
// are going to use (remember orderedFragments includes any
// container fragments)
if (ok) {
processAnnotations(
orderedFragments, webXml.isMetadataComplete(), javaClassCache);
}
// Cache, if used, is no longer required so clear it
javaClassCache.clear();
}
查看processAnnotationsWebResource方法
protected void processAnnotationsWebResource(WebResource webResource,
WebXml fragment, boolean handlesTypesOnly,
Map<String,JavaClassCacheEntry> javaClassCache) {
//看看是否是個(gè)目錄
if (webResource.isDirectory()) {
WebResource[] webResources =
webResource.getWebResourceRoot().listResources(
webResource.getWebappPath());
if (webResources.length > 0) {
if (log.isDebugEnabled()) {
log.debug(sm.getString(
"contextConfig.processAnnotationsWebDir.debug",
webResource.getURL()));
}
//遍歷目錄
for (WebResource r : webResources) {
//遞歸處理
processAnnotationsWebResource(r, fragment, handlesTypesOnly, javaClassCache);
}
}
} else if (webResource.isFile() &&
webResource.getName().endsWith(".class")) {
try (InputStream is = webResource.getInputStream()) {
//如果是類文件的話 開始處理
processAnnotationsStream(is, fragment, handlesTypesOnly, javaClassCache);
} catch (IOException e) {
log.error(sm.getString("contextConfig.inputStreamWebResource",
webResource.getWebappPath()),e);
} catch (ClassFormatException e) {
log.error(sm.getString("contextConfig.inputStreamWebResource",
webResource.getWebappPath()),e);
}
}
}
查看processAnnotationsStream方法
protected void processAnnotationsStream(InputStream is, WebXml fragment,
boolean handlesTypesOnly, Map<String,JavaClassCacheEntry> javaClassCache)
throws ClassFormatException, IOException {
ClassParser parser = new ClassParser(is);
JavaClass clazz = parser.parse();
checkHandlesTypes(clazz, javaClassCache);
if (handlesTypesOnly) {
return;
}
//處理開始
processClass(fragment, clazz);
}
protected void processClass(WebXml fragment, JavaClass clazz) {
AnnotationEntry[] annotationsEntries = clazz.getAnnotationEntries();
if (annotationsEntries != null) {
String className = clazz.getClassName();
for (AnnotationEntry ae : annotationsEntries) {
String type = ae.getAnnotationType();
if ("Ljavax/servlet/annotation/WebServlet;".equals(type)) {
processAnnotationWebServlet(className, ae, fragment);
//判斷是否webFilter注解 如果是就添加到 webxml配置對(duì)象中
}else if ("Ljavax/servlet/annotation/WebFilter;".equals(type)) {
processAnnotationWebFilter(className, ae, fragment);
}else if ("Ljavax/servlet/annotation/WebListener;".equals(type)) {
fragment.addListener(className);
} else {
// Unknown annotation - ignore
}
}
}
}
從上面可以看出原來(lái)掃描類路徑的時(shí)候,就是先遍歷文件夾 遍歷文件夾下類文件 反射查看是否是一個(gè)帶有WebFilter注解的類
如果是就添加到web.xml中set集合中,而那個(gè)set集合是有序的linkedset
所有順序就是遞歸遍歷文件夾的順序 一切就看 遞歸的時(shí)候如何獲取下級(jí)文件夾的代碼了 看它是否進(jìn)行排序了?
也就是說(shuō)由如下代碼決定的
WebResource[] webResources =
webResource.getWebResourceRoot().listResources(
webResource.getWebappPath());
點(diǎn)開這段代碼
protected WebResource[] listResources(String path, boolean validate) {
if (validate) {
path = validate(path);
}
String[] resources = list(path, false);
WebResource[] result = new WebResource[resources.length];
for (int i = 0; i < resources.length; i++) {
if (path.charAt(path.length() - 1) == '/') {
result[i] = getResource(path + resources[i], false, false);
} else {
result[i] = getResource(path + '/' + resources[i], false, false);
}
}
return result;
}
//繼續(xù)
private String[] list(String path, boolean validate) {
if (validate) {
path = validate(path);
}
// Set because we don't want duplicates
// LinkedHashSet to retain the order. It is the order of the
// WebResourceSet that matters but it is simpler to retain the order
// over all of the JARs.
HashSet<String> result = new LinkedHashSet<>();
for (List<WebResourceSet> list : allResources) {
for (WebResourceSet webResourceSet : list) {
if (!webResourceSet.getClassLoaderOnly()) {
String[] entries = webResourceSet.list(path);
for (String entry : entries) {
result.add(entry);
}
}
}
}
return result.toArray(new String[result.size()]);
}
//繼續(xù)
public String[] list(String path) {
checkPath(path);
String webAppMount = getWebAppMount();
if (path.startsWith(webAppMount)) {
File f = file(path.substring(webAppMount.length()), true);
if (f == null) {
return EMPTY_STRING_ARRAY;
}
//就到這里了 我們可以看到 它沒(méi)有排序就是調(diào)用了
//file類的list方法
String[] result = f.list();
if (result == null) {
return EMPTY_STRING_ARRAY;
} else {
return result;
}
} else {
if (!path.endsWith("/")) {
path = path + "/";
}
if (webAppMount.startsWith(path)) {
int i = webAppMount.indexOf('/', path.length());
if (i == -1) {
return new String[] {webAppMount.substring(path.length())};
} else {
return new String[] {
webAppMount.substring(path.length(), i)};
}
}
return EMPTY_STRING_ARRAY;
}
}來(lái)來(lái)打開file類的list方法看看
所以最終 web.xml收集到所有filter的set集合 如果采用的是注解方式 沒(méi)有任何順序可言的.
然后接下來(lái)的代碼我們就不看了無(wú)非就是將收集到的filter集合轉(zhuǎn)換成數(shù)組 設(shè)置給StandardContext對(duì)象
4.得出結(jié)論
如果采用web.xml寫的filter執(zhí)行順序跟書寫順序有關(guān)
而采用注解方式的是沒(méi)有順序可言的!!!!
而采用注解方式的是沒(méi)有順序可言的!!!!
而采用注解方式的是沒(méi)有順序可言的!!!!
實(shí)踐出真知!切記人云亦云!有問(wèn)題找源碼!!!
猜你喜歡:
Java中級(jí)程序員培訓(xùn)課程