首頁技術文章正文

filter的執(zhí)行順序解密[java培訓]

更新時間:2020-07-08 來源:黑馬程序員 瀏覽量:

1.引言

我們在編寫javaweb程序的時候,時常會用filter這個組件,它能將我們一些通用邏輯抽取出來,在servlet執(zhí)行業(yè)務邏輯之前運行,

達到簡化代碼和復用的目的.比如最常用的場景全站編碼和登錄驗證功能.

servlet3.0以前我們只能通過web.xml的方式配置filter,并且多個filter的執(zhí)行順序是根據(jù)你web.xml中書寫順序來決定的.

servlet3.0以后,提供了注解的方式注入filter,只需要在filter類上加上@WebFilter()注解即可,大大的簡化了開發(fā)復雜度.

2.拋出問題

注解的方式書寫的filter的執(zhí)行順序又是如何的呢?

網(wǎng)上的很多資料都說是根據(jù)filter的類名來決定,也有說是根據(jù)filter的注解的name屬性值的字母順序來決定的.

對不對呢?

2.驗證問題

我們創(chuàng)建了三個filter 來驗證此問題

filter1號

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號

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號

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 {

   }

}


配上一個servlet 來訪問試試

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");
   }
}


驗證結果:

1594193918418_filter01.jpg


3.查看源碼

可以從上面驗證看出 好像并不是根據(jù)類名或者filter的name屬性的字母排序執(zhí)行,那到底是根據(jù)什么執(zhí)行的呢?

點開源碼,我們一點點探尋它的秘密。

需要搞清楚如下問題

1.filterChain是什么時候執(zhí)行的呢?

2.filterChain中的filter來源何處?

3.standardContext什么時候開始收集的過濾器集合

我們先查詢第一段源碼 解密filterChain是什么時候執(zhí)行的

1594193944760_filter02.jpg


//在org.apache.catalina.core.StandardWrapperValve 類中有如下一個方法

public final void invoke(Request request, Response response)
       throws IOException, ServletException {
   //省略掉大段無關代碼
      //0.生命servlet對象
       Servlet servlet = null;
       /**
        * 省略一大段無關代碼
        */
       try {
           if (!unavailable) {
              //1.這里創(chuàng)建正在訪問的servlet對象
               servlet = wrapper.allocate();
           }
       } catch (UnavailableException e) {
           //省略掉大段無關代碼
       }
   
       //省略大段無關代碼
 
    //2.創(chuàng)建過濾器鏈對象
    ApplicationFilterChain filterChain =
               ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
       try {
           if ((servlet != null) && (filterChain != null)) {
               // Swallow output if needed
               if (context.getSwallowOutput()) {
                  //省略掉大段無關代碼
               } else {
                   if (request.isAsyncDispatching()) {
                       request.getAsyncContextInternal().doInternalDispatch();
                   } else {
                      //3.執(zhí)行過濾鏈對象doFilter方法
                       filterChain.doFilter
                           (request.getRequest(), response.getResponse());
                   }
               }

           }
       } catch (ClientAbortException | CloseNowException e) {
        //省略掉大段無關代碼
       }
   }


我們一會兒回過頭來看它是如何創(chuàng)建過濾器鏈對象的代碼,我們先來看他是如何執(zhí)行過濾器鏈的,

過濾器鏈對象的實現(xiàn)為:


package org.apache.catalina.core;
//我們只保留 跟執(zhí)行序列有關的代碼

public final class ApplicationFilterChain implements FilterChain {
  //當前正在執(zhí)行的filter索引
   private int pos = 0;
  //總共有多少個filter匹配上了
   private int n = 0;
  //關聯(lián)的要執(zhí)行的servlet對象
   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 ) {
           //刪除無關代碼
       } else {
          //0.執(zhí)行內容的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) {
          //這個地方主義有個pos++ 進來一次 ++一次
           ApplicationFilterConfig filterConfig = filters[pos++];
           try {
               Filter filter = filterConfig.getFilter();

               //刪除大段無關代碼
             
               if( Globals.IS_SECURITY_ENABLED ) {
                  //刪除大段無關代碼
               } else {
                  //執(zhí)行過濾器鏈中的過濾器的doFilter方法
                  //而我們的過濾器中滿足條件后 放行 放行就會跳轉回來執(zhí)行過濾器鏈的
                  //的doFilter 也就是又回來執(zhí)行第二個
                   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 {
           //刪除大段無關代碼
           // Use potentially wrapped request from this point
           if ((request instanceof HttpServletRequest) &&
                   (response instanceof HttpServletResponse) &&
                   Globals.IS_SECURITY_ENABLED ) {
               //刪除大段無關代碼
           } else {
              //如果沒有需要執(zhí)行的filter就會執(zhí)行 servlet的service方法 也就是我們寫的業(yè)務邏輯
               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ù)組 也就是添加進去匹配上過濾器對象是有序的 添加的時候就決定了!!!

那么它是什么時候添加的呢?

2.filterChain中的filter來源何處?

其實我們在在org.apache.catalina.core.StandardWrapperValve  類的invoke方法中

ApplicationFilterChain filterChain =
               ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);


點開這段代碼查詢把!

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 對象
    //但是對象里還沒有filter對象
       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對象 注冊的所有的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ù)請求路徑匹配添加
       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對象時候,從ServletContext獲取所有注冊的filter的數(shù)組 取出需要的添加到這次請求創(chuàng)建的filterChain對象中

而且servletContext對象的注冊的所有的過濾器本身就是一個數(shù)組 本身就是有序的,所以遍歷匹配的時候,也就是有序的!

3、ServletContext什么時候開始收集的數(shù)組,從哪來的呢?

這個要從tomcat啟動的時候來看了!

1594193999864_filter03.jpg

而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 配置文件對象
    //也就是它就代表我們項目的配置相關的信息
       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();

       //省略大段無關代碼

       if  (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
           // Steps 4 & 5.
          //掃描編譯的類文件 尋找注解方式書寫的servlet filter listener
           processClasses(webXml, orderedFragments);
       }
   //省略大段無關代碼
   }


從上面代碼 可以看出web,xml配置的filter肯定是有序的了 解析的時候 就會收集到webXml對象的

//采用的是linkedHashset來存儲的 是有序的
private final Set<FilterMap> filterMaps = new LinkedHashSet<>();


解析xml過程我們就不看了 人家才是的digester的xml解析框架來做.

我們來查看processClasses(webXml, orderedFragments); 這個方法是解析注解用的

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) {
          //獲取項目下的類路徑
           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) {
   //看看是否是個目錄
       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配置對象中
               }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
               }
           }
       }
   }

從上面可以看出原來掃描類路徑的時候,就是先遍歷文件夾 遍歷文件夾下類文件 反射查看是否是一個帶有WebFilter注解的類

如果是就添加到web.xml中set集合中,而那個set集合是有序的linkedset

所有順序就是遞歸遍歷文件夾的順序 一切就看 遞歸的時候如何獲取下級文件夾的代碼了 看它是否進行排序了?

也就是說由如下代碼決定的

WebResource[] webResources =
                   webResource.getWebResourceRoot().listResources(
                           webResource.getWebappPath());

點開這段代碼

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;
           }
          //就到這里了 我們可以看到 它沒有排序就是調用了
          //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;
       }
   }


來來打開file類的list方法看看

1594194040614_filter04.jpg


所以最終 web.xml收集到所有filter的set集合 如果采用的是注解方式 沒有任何順序可言的.

然后接下來的代碼我們就不看了無非就是將收集到的filter集合轉換成數(shù)組 設置給StandardContext對象

4.得出結論

如果采用web.xml寫的filter執(zhí)行順序跟書寫順序有關

而采用注解方式的是沒有順序可言的!!!!

而采用注解方式的是沒有順序可言的!!!!

而采用注解方式的是沒有順序可言的!!!!

實踐出真知!切記人云亦云!有問題找源碼!!!

猜你喜歡:
Java中級程序員培訓課程


分享到:
在線咨詢 我要報名
和我們在線交談!