原始碼分析過濾器與攔截器的區別
博主最近剛拿到一個微服務的新專案,邊研究邊分析從框架基礎開始慢慢帶領大家研究微服務的一些東西,這次給大家分析下Springboot中的過濾器和攔截器的區別。雖然上次分析過過濾器,但是主要是分析的cas流程,所以就沒太深入,大家也可以看一下的啊
cas原始碼分析:https://www.cnblogs.com/guoxiaoyu/p/13280259.html
好的,正題開始:首先講解一下Springboot中如何進行新增過濾器、進行過濾器過濾請求。新增示例必須來一下
1 @Configuration 2 public class WebConfiguration{ 3 4 @Bean 5 public FilterRegistrationBean testFilterByMe(){ 6 FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); 7 filterRegistrationBean.setFilter(new TestFilterByMe()); 8 filterRegistrationBean.setOrder(1); 9 return filterRegistrationBean; 10 } 11 }
我們過濾器為什麼要新增到FilterRegistrationBean中,不新增可不可以,為什麼用@WebFilter註解也可以呢,用@Component可不可以以的呢?博主今天就通過原始碼給大家講解一下這幾個問題
首先我們的Springboot開始啟動後,會進行建立bean和web伺服器tomcat,原始碼附上:
1 @Override 2 protected void onRefresh() { 3 //onRefresh方法就是掃描包,解析配置類,再建立bean的過程,先不帶大家解析這個onRefresh方法了跟本次沒啥太大關係, 4 //我們的所有類都在這裡建立完成,包括我們建立的過濾器 5 super.onRefresh(); 6 try { 7 //開始建立web伺服器tomcat,所以Springboot才可以不依賴web容器,自己就可以啟動成功並進行訪問 8 createWebServer(); 9 } 10 catch (Throwable ex) { 11 throw new ApplicationContextException("Unable to start web server", ex); 12 } 13 }
createWebServer()這個方法的原始碼我就不貼上了,大家可以自己看一下原始碼,最後就會看到new tomcat();並進行啟動tomcat。啟動容器後當然是開始進行初始化。
1 private void selfInitialize(ServletContext servletContext) throws ServletException { 2 prepareWebApplicationContext(servletContext); 3 registerApplicationScope(servletContext); 4 WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext); 5 //getServletContextInitializerBeans()這個方法進開始進行解析並新增filter過濾器 了 6 for (ServletContextInitializer beans : getServletContextInitializerBeans()) { 7 beans.onStartup(servletContext); 8 } 9 }
現在才到了新增過濾器最關鍵的部分,這個部分已經基本把上面的三個問題的答案告訴大家了,詳情原始碼如下:
1 //開始新增過濾器 2 public ServletContextInitializerBeans(ListableBeanFactory beanFactory, 3 Class<? extends ServletContextInitializer>... initializerTypes) { 4 this.initializers = new LinkedMultiValueMap<>(); 5 this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes) 6 : Collections.singletonList(ServletContextInitializer.class); 7 //這裡實現的新增形式是通過FilterRegistrationBean型別註冊的 8 addServletContextInitializerBeans(beanFactory); 9 //這裡是通過beanfactory中獲取filter型別過濾器後新增進來的,這就明白了,只要讓spring掃描到, 10 //過濾器自己實現了filter介面,你就會給新增到過濾器鏈 11 addAdaptableBeans(beanFactory); 12 //都會新增到initializers這一個map中 13 List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream() 14 .flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE)) 15 .collect(Collectors.toList()); 16 this.sortedList = Collections.unmodifiableList(sortedInitializers); 17 logMappings(this.initializers); 18 }org/springframework/boot/web/servlet/ServletContextInitializerBeans
一個一個方法分析一下,讓大家看個明白到底是怎麼回事,為什麼這三種方法都可以實現新增過濾器
1 //獲取我們的實現FilterRegistrationBean類的過濾器 2 private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) { 3 for (Class<? extends ServletContextInitializer> initializerType : this.initializerTypes) { 4 //獲取type為ServletContextInitializer的排好序的類,跟是否實現order類無關! 5 for (Entry<String, ? extends ServletContextInitializer> initializerBean : getOrderedBeansOfType(beanFactory, 6 initializerType)) { 7 //這時候就開始判斷實現FilterRegistrationBean類的過濾器 8 addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory); 9 } 10 } 11 }org/springframework/boot/web/servlet/ServletContextInitializerBeans.java
獲取bean時debug,觀察一下,最後會篩選出來我們FilterRegistrationBean的過濾器,為什麼呢?因為這個類的上級實現了ServletContextInitializer
再來看一下新增的過程,就知道filter要註冊到FilterRegistrationBean中的原因了,
1 private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer, 2 ListableBeanFactory beanFactory) { 3 if (initializer instanceof ServletRegistrationBean) { 4 Servlet source = ((ServletRegistrationBean<?>) initializer).getServlet(); 5 addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source); 6 } 7 //在這裡進行的新增的過程 8 else if (initializer instanceof FilterRegistrationBean) { 9 Filter source = ((FilterRegistrationBean<?>) initializer).getFilter(); 10 addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source); 11 } 12 else if (initializer instanceof DelegatingFilterProxyRegistrationBean) { 13 String source = ((DelegatingFilterProxyRegistrationBean) initializer).getTargetBeanName(); 14 addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source); 15 } 16 else if (initializer instanceof ServletListenerRegistrationBean) { 17 EventListener source = ((ServletListenerRegistrationBean<?>) initializer).getListener(); 18 addServletContextInitializerBean(EventListener.class, beanName, initializer, beanFactory, source); 19 } 20 else { 21 addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory, 22 initializer); 23 } 24 }org/springframework/boot/web/servlet/ServletContextInitializerBeans
我們再來看一下另一種新增的方法
1 //另一種新增過濾器的方法在這裡 2 protected void addAdaptableBeans(ListableBeanFactory beanFactory) { 3 MultipartConfigElement multipartConfig = getMultipartConfig(beanFactory); 4 addAsRegistrationBean(beanFactory, Servlet.class, new ServletRegistrationBeanAdapter(multipartConfig)); 5 //從bean工廠中獲取為Filter型別的類,所以只要我們把我們已經實現Filter介面的類交給spring,beanFactory中有我們的類就可以實現 6 addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter()); 7 for (Class<?> listenerType : ServletListenerRegistrationBean.getSupportedTypes()) { 8 addAsRegistrationBean(beanFactory, EventListener.class, (Class<EventListener>) listenerType, 9 new ServletListenerRegistrationBeanAdapter()); 10 } 11 }org/springframework/boot/web/servlet/ServletContextInitializerBeans
其實最後獲取出來後,都是進行建立FilterRegistrationBean
1 private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type, 2 Class<B> beanType, RegistrationBeanAdapter<T> adapter) { 3 //從beanfactory中獲取為filter型別的bean 4 List<Map.Entry<String, B>> entries = getOrderedBeansOfType(beanFactory, beanType, this.seen); 5 for (Entry<String, B> entry : entries) { 6 String beanName = entry.getKey(); 7 B bean = entry.getValue(); 8 if (this.seen.add(bean)) { 9 //剩下其他自動實現的建立過程,也是建立一個FilterRegistrationBean並返回 10 RegistrationBean registration = adapter.createRegistrationBean(beanName, bean, entries.size()); 11 int order = getOrder(bean); 12 registration.setOrder(order); 13 this.initializers.add(type, registration); 14 if (logger.isTraceEnabled()) { 15 logger.trace("Created " + type.getSimpleName() + " initializer for bean '" + beanName + "'; order=" 16 + order + ", resource=" + getResourceDescription(beanName, beanFactory)); 17 } 18 } 19 } 20 } 21 @Override 22 public RegistrationBean createRegistrationBean(String name, Filter source, int totalNumberOfSourceBeans) { 23 FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>(source); 24 bean.setName(name); 25 return bean; 26 }org/springframework/boot/web/servlet/ServletContextInitializerBeans
現在已經把過濾器找的了並且已經新增成功了,開始進行註冊時呼叫的是onstartup方法,註冊到filterDefs這個map中,下面初始化會用到
這裡開始進行過濾器的初始化,new ApplicationFilterConfig方法就需要大家自己去debug了,至少加深一下印象,裡面會進行初始化,呼叫init方法
1 //開始過濾器的初始化 2 public boolean filterStart() { 3 4 if (getLogger().isDebugEnabled()) { 5 getLogger().debug("Starting filters"); 6 } 7 // Instantiate and record a FilterConfig for each defined filter 8 boolean ok = true; 9 synchronized (filterConfigs) { 10 filterConfigs.clear(); 11 //filterDefs這個map就是剛才新增進來的過濾器map 12 for (Entry<String,FilterDef> entry : filterDefs.entrySet()) { 13 String name = entry.getKey(); 14 if (getLogger().isDebugEnabled()) { 15 getLogger().debug(" Starting filter '" + name + "'"); 16 } 17 try { 18 //在這裡會進行fileter的init方法 19 ApplicationFilterConfig filterConfig = 20 new ApplicationFilterConfig(this, entry.getValue()); 21 filterConfigs.put(name, filterConfig); 22 } catch (Throwable t) { 23 t = ExceptionUtils.unwrapInvocationTargetException(t); 24 ExceptionUtils.handleThrowable(t); 25 getLogger().error(sm.getString( 26 "standardContext.filterStart", name), t); 27 ok = false; 28 } 29 } 30 }org/apache/catalina/core/StandardContext
到這裡,過濾器初始化就完成了也把開頭的三個問題給大家講解明白了,剩下的就是過濾請求的過程了,看一下請求過來時的原始碼處理
1 // Create the filter chain for this request 2 //當有請求過來時,首先會呼叫過濾器,進行過濾,這裡會進行過濾器陣列的建立 3 ApplicationFilterChain filterChain = 4 ApplicationFilterFactory.createFilterChain(request, wrapper, servlet); 5 6 // Call the filter chain for this request 7 // NOTE: This also calls the servlet's service() method 8 Container container = this.container; 9 try { 10 if ((servlet != null) && (filterChain != null)) { 11 // Swallow output if needed 12 if (context.getSwallowOutput()) { 13 try { 14 SystemLogHandler.startCapture(); 15 if (request.isAsyncDispatching()) { 16 request.getAsyncContextInternal().doInternalDispatch(); 17 } else { 18 filterChain.doFilter(request.getRequest(), 19 response.getResponse()); 20 } 21 } finally { 22 String log = SystemLogHandler.stopCapture(); 23 if (log != null && log.length() > 0) { 24 context.getLogger().info(log); 25 } 26 } 27 } else { 28 if (request.isAsyncDispatching()) { 29 request.getAsyncContextInternal().doInternalDispatch(); 30 } else { 31 filterChain.doFilter 32 (request.getRequest(), response.getResponse()); 33 } 34 } 35 36 }org/apache/catalina/core/StandardWrapperValve
1 //陣列結構可以在這裡檢視 2 void addFilter(ApplicationFilterConfig filterConfig) { 3 4 // Prevent the same filter being added multiple times 5 for(ApplicationFilterConfig filter:filters) 6 if(filter==filterConfig) 7 return; 8 9 if (n == filters.length) { 10 ApplicationFilterConfig[] newFilters = 11 new ApplicationFilterConfig[n + INCREMENT]; 12 System.arraycopy(filters, 0, newFilters, 0, n); 13 filters = newFilters; 14 } 15 filters[n++] = filterConfig; 16 17 }ApplicationFilter資料結構
建立後會進行對請求的過濾,原始碼:
1 //過濾器開始過濾 2 private void internalDoFilter(ServletRequest request, 3 ServletResponse response) 4 throws IOException, ServletException { 5 6 // Call the next filter if there is one 7 //過濾器陣列大小 8 if (pos < n) { 9 //每呼叫一次都會從陣列中自增1 pos++ 10 ApplicationFilterConfig filterConfig = filters[pos++]; 11 try { 12 Filter filter = filterConfig.getFilter(); 13 14 if (request.isAsyncSupported() && "false".equalsIgnoreCase( 15 filterConfig.getFilterDef().getAsyncSupported())) { 16 request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE); 17 } 18 if( Globals.IS_SECURITY_ENABLED ) { 19 final ServletRequest req = request; 20 final ServletResponse res = response; 21 Principal principal = 22 ((HttpServletRequest) req).getUserPrincipal(); 23 24 Object[] args = new Object[]{req, res, this}; 25 SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal); 26 } else { 27 //每次都會呼叫doFilter方法,在doFilter方法中呼叫internalDoFilter,就是一直回撥,直到所有過濾器走完 28 filter.doFilter(request, response, this); 29 } 30 } catch (IOException | ServletException | RuntimeException e) { 31 throw e; 32 } catch (Throwable e) { 33 e = ExceptionUtils.unwrapInvocationTargetException(e); 34 ExceptionUtils.handleThrowable(e); 35 throw new ServletException(sm.getString("filterChain.filter"), e); 36 } 37 //當有過濾器直接返回,並沒有繼續回撥時,回直接return,不會處理該請求,就是下面的步驟 38 return; 39 } 40 //當所有過濾器走完後,將會處理請求 41 // We fell off the end of the chain -- call the servlet instance 42 try { 43 if (ApplicationDispatcher.WRAP_SAME_OBJECT) { 44 lastServicedRequest.set(request); 45 lastServicedResponse.set(response); 46 } 47 48 if (request.isAsyncSupported() && !servletSupportsAsync) { 49 request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, 50 Boolean.FALSE); 51 } 52 // Use potentially wrapped request from this point 53 if ((request instanceof HttpServletRequest) && 54 (response instanceof HttpServletResponse) && 55 Globals.IS_SECURITY_ENABLED ) { 56 final ServletRequest req = request; 57 final ServletResponse res = response; 58 Principal principal = 59 ((HttpServletRequest) req).getUserPrincipal(); 60 Object[] args = new Object[]{req, res}; 61 SecurityUtil.doAsPrivilege("service", 62 servlet, 63 classTypeUsedInService, 64 args, 65 principal); 66 } else { 67 //就是這裡直接呼叫dsipatcherservlet的service方法去轉發doget,dopost方法的, 68 //剩下的就是攔截器的知識點了: 69 servlet.service(request, response); 70 } 71 } catch (IOException | ServletException | RuntimeException e) { 72 throw e; 73 } catch (Throwable e) { 74 e = ExceptionUtils.unwrapInvocationTargetException(e); 75 ExceptionUtils.handleThrowable(e); 76 throw new ServletException(sm.getString("filterChain.servlet"), e); 77 } finally { 78 if (ApplicationDispatcher.WRAP_SAME_OBJECT) { 79 lastServicedRequest.set(null); 80 lastServicedResponse.set(null); 81 } 82 } 83 }org/apache/catalina/core/ApplicationFilterChain
到此建立以及過濾請求的流程分析也就結束了,和攔截器的建立以及攔截分析做一下對比,分析一下兩者的區別,如果不知道攔截器的建立以及流程處理可以看一下我的另一篇文章:https://www.cnblogs.com/guoxiaoyu/p/13402861.html
相同點:
- 都需要交給spring進行管理,雖然filter本身是servlet,但是如果不給spring管理,根本不會新增成功,也不會過濾請求
- 都會在請求真正被處理前進行攔截過濾,如果不符合條件會直接返回,不會處理請求
- 兩者都可以指定執行順序
差異點:
- 過濾器先註冊,攔截器後註冊
- 過濾器先執行,攔截器後執行,攔截器可以在請求執行後繼續處理其他事情,過濾器只有一個過濾的方法
- 過濾器執行時是基於函式回撥,而攔截器執行是直接從陣列中獲取,一個一個執行,作者沒有看到哪裡用到了反射,網上好多說是反射,攔截器的三個方法都是從陣列中獲取然後一個一個呼叫方法進行的,只有在處理請求的時候才用到了invoke反射