Java過濾器鏈原理解析
在很多Java Web專案中我們會在web.xml中配置一些過濾器來攔截請求,比如下面解決亂碼的編碼過濾器:
但是讓我一直困惑的是,這些過濾器是由誰來呼叫的?又是誰將它們組織起來,並確保他們的執行順序的?<filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <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>
今天使用Spring cloud用程式碼的方法配置過濾器的時候(不再是配到web.xml中,Spring clound自帶web容器,提供開箱即用的服務,所以以前很多需要配到web容器的配置都交給Spring來管理了,在學習Spring cloud的時候發現這貨真的是想統一全世界呀-_-),發現自己定義的一個過濾器死活不執行,就打算將這個問題深挖到底。
過濾器demo如下(為保護公司祕密,重要程式碼已刪除,各位見諒哈):
本以為這樣寫了之後過濾器就能工作了,可是請求進來以後發現過濾器未執行。後來想到Spring的工作都是交給註冊到beanFacotry中的一個個bean來完成的,所以可能需要把這個過濾器註冊到beanFactory中,就像這樣:@WebFilter(filterName="FilterDemo", urlPatterns={"/**"}) @Order(1) //當有多個filter時,指定filter的順序 public class SecurityFilter implements Filter{ private static final Logger LOGGER = LoggerFactory.getLogger( FilterDemo.class ); @Override public void destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //TODO do some filter action; chain.doFilter(request, response); } @Override public void init(FilterConfig arg0) throws ServletException { // TODO Auto-generated method stub } }
加了註解之後果然立馬見效,看來我的直覺還是挺準的,嘻嘻~_~@Configuration //表明這是一個Spring配置檔案 @EnableSwagger2 //swagger是一個restful介面的文件線上自動生成+功能測試功能框架 @RefreshScope //允許當Spring cloud config配置檔案改動之後,依賴配置的bean自動更新而不用重啟服務 public class GlobalBeanConfig { //將filter加入到Spring配置中,否則只把filter類寫出來,不加入配置還是無法起作用 @Bean //將xml形式的<bean></bean>採用程式碼來配置 public Filter filterDemo(){ return new FilterDemo(); } @Bean(name = "loggerInteceptor") public GlobalAspectInteceptor getLoggerInteceptor() { return new GlobalAspectInteceptor(); } @Bean public ThreadPoolTaskExecutor globalTaskExecutor( @Value("${global.thread.pool.corePoolSize}") Integer corePoolSize, @Value("${global.thread.pool.maxPoolSize}") Integer maxPoolSize, @Value("${global.thread.pool.queueCapacity}") Integer queueCapacity ) { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize( corePoolSize ); executor.setMaxPoolSize( maxPoolSize ); executor.setQueueCapacity( queueCapacity ); executor.setWaitForTasksToCompleteOnShutdown(true); executor.setThreadGroupName("globalTaskExecutor"); executor.setRejectedExecutionHandler( new ThreadPoolExecutor.DiscardPolicy() ); executor.initialize(); return executor; } }
好了,既然我們知道怎麼用工具了,下面就來拆工具,看看how it works。通過斷點除錯,發現過濾器中兩個關鍵的類在起作用:ApplicationFilterConfig,ApplicationFilterChain。
這兩個類是org.apache.catalina.core包下面的,可以肯定是tomcat容器來管理過濾器鏈了。
接下來看ApplicationFilterConfig,先上部分原始碼:
public final class ApplicationFilterConfig implements FilterConfig, Serializable { private static final long serialVersionUID = 1L; static final StringManager sm = StringManager.getManager("org.apache.catalina.core"); private static final Log log = LogFactory.getLog(ApplicationFilterConfig.class); private static final List<String> emptyString = Collections.emptyList(); private final transient Context context; private transient Filter filter = null; private final FilterDef filterDef; private transient InstanceManager instanceManager; private ObjectName oname; ApplicationFilterConfig(Context context, FilterDef filterDef) throws ClassCastException, ClassNotFoundException, IllegalAccessException, InstantiationException, ServletException, InvocationTargetException, NamingException, IllegalArgumentException, NoSuchMethodException, SecurityException { this.context = context; this.filterDef = filterDef; if(filterDef.getFilter() == null) { this.getFilter(); } else { this.filter = filterDef.getFilter(); this.getInstanceManager().newInstance(this.filter); this.initFilter(); } } }通過分析可以發現,這個類是就是用來持有一個過濾器的,構造方法傳入的context想必就是tomcat配置檔案下的context.xml配置的應用環境,fliterDef就是過濾器的描述資訊,應該是通過配置在web.xml中(或者其他什麼地方)的filter引數來構造的。
好,重點來啦-ApplicationFilterChain,名字就暴露了它的作用,就是它將一個個分散的過濾器組織起來的。結合上面我猜測,它裡面應該有一個數組或列表來儲存ApplicationFilterConfig,還有一個過濾器遊標,來記錄當前過濾器走到哪兒了。原始碼(部分)如下:
public final class ApplicationFilterChain implements FilterChain { private static final ThreadLocal<ServletRequest> lastServicedRequest; private static final ThreadLocal<ServletResponse> lastServicedResponse; public static final int INCREMENT = 10; private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0]; private int pos = 0; private int n = 0; private Servlet servlet = null; private boolean servletSupportsAsync = false; private static final StringManager sm; private static final Class<?>[] classType; private static final Class<?>[] classTypeUsedInService; public ApplicationFilterChain() { } public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if(Globals.IS_SECURITY_ENABLED) { final ServletRequest req = request; final ServletResponse res = response; try { AccessController.doPrivileged(new PrivilegedExceptionAction() { public Void run() throws ServletException, IOException { ApplicationFilterChain.this.internalDoFilter(req, res); return null; } }); } catch (PrivilegedActionException var7) { Exception e = var7.getException(); if(e instanceof ServletException) { throw (ServletException)e; } if(e instanceof IOException) { throw (IOException)e; } if(e instanceof RuntimeException) { throw (RuntimeException)e; } throw new ServletException(e.getMessage(), e); } } else { this.internalDoFilter(request, response); } } private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if(this.pos < this.n) { ApplicationFilterConfig e1 = this.filters[this.pos++]; try { Filter res1 = e1.getFilter(); if(request.isAsyncSupported() && "false".equalsIgnoreCase(e1.getFilterDef().getAsyncSupported())) { request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE); } if(Globals.IS_SECURITY_ENABLED) { Principal principal1 = ((HttpServletRequest)request).getUserPrincipal(); Object[] args1 = new Object[]{request, response, this}; SecurityUtil.doAsPrivilege("doFilter", res1, classType, args1, principal1); } else { res1.doFilter(request, response, this); } } catch (ServletException | RuntimeException | IOException var15) { throw var15; } catch (Throwable var16) { Throwable res = ExceptionUtils.unwrapInvocationTargetException(var16); ExceptionUtils.handleThrowable(res); throw new ServletException(sm.getString("filterChain.filter"), res); } } else { try { if(ApplicationDispatcher.WRAP_SAME_OBJECT) { lastServicedRequest.set(request); lastServicedResponse.set(response); } if(request.isAsyncSupported() && !this.servletSupportsAsync) { request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE); } if(request instanceof HttpServletRequest && response instanceof HttpServletResponse && Globals.IS_SECURITY_ENABLED) { Principal principal = ((HttpServletRequest)request).getUserPrincipal(); Object[] args = new Object[]{request, response}; SecurityUtil.doAsPrivilege("service", this.servlet, classTypeUsedInService, args, principal); } else { this.servlet.service(request, response); } } catch (ServletException | RuntimeException | IOException var17) { throw var17; } catch (Throwable var18) { Throwable e = ExceptionUtils.unwrapInvocationTargetException(var18); ExceptionUtils.handleThrowable(e); throw new ServletException(sm.getString("filterChain.servlet"), e); } finally { if(ApplicationDispatcher.WRAP_SAME_OBJECT) { lastServicedRequest.set((Object)null); lastServicedResponse.set((Object)null); } } } } ......}果然,欄位裡面有一個ApplicationFilterConfig[]用來儲存一系列過濾器,pos用來儲存當前過濾器位置,還有其他欄位就不深入下去了,有興趣的小夥伴可以自己探索。
再來看兩個重點方法:doFilter,internalDoFilter
doFilter的最終目的只有一個,呼叫internalDoFilter,中間可能會增加一些安全策略,估計Globals.IS_SECURITY_ENABLE與是否開啟https服務有關,具體沒仔細研究過。
internalDoFilter的最終目的也只有一個,就是調當前pos指向的過濾器鏈中的某一個filter的doFilter(request, response, this)方法,中間可能會增加一些安全策略,以及當所有過濾器呼叫完了,進行的一些收尾清理工作,包括呼叫servlet.service(request, response)方法,來處理真正的請求,以及清除threadLocal中儲存的當前的request和response,為下一次請求做準備。
再把流程梳理一遍:
一個request請求進來了,先把自己交給filterChain;
filterChain啟動過濾器鏈,從頭開始,把request交給第一個filter,並把自己傳給filter;
filter在doFilter裡做完自己的過濾邏輯,再呼叫filterChain的doFilter,以啟動下一個過濾器;
filterChain遊標移動,啟動下一個過濾器,如此迴圈下去......
過濾器遊標走到鏈的末尾,filterChain執行收尾工作;
最後給一個簡單的流程圖: