源码分析过滤器与拦截器的区别

博主最近刚拿到一个微服务的新项目,边研究边分析从框架基础开始慢慢带领大家研究微服务的一些东西,这次给大家分析下Springboot中的过滤器和拦截器的区别。虽然上次分析过过滤器,但是主要是分析的cas流程,所以就没太深入,大家也可以看一下的啊

cas源码分析:www.cnblogs.com/guoxiaoyu/p...

好的,正题开始:首先讲解一下Springboot中如何进行添加过滤器、进行过滤器过滤请求。添加示例必须来一下

java 复制代码
 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,源码附上:

typescript 复制代码
 1 @Override
 2     protected void onRefresh() {
 3     //onRefresh方法就是扫描包,解析配置类的过程,原生spring中是一个空方法,这里进行重写用于创建tomcat服务器
 4         super.onRefresh();
 5         try {
 6             //开始创建web服务器tomcat,所以Springboot才可以不依赖web容器,自己就可以启动成功并进行访问
 7             createWebServer();
 8         }
 9         catch (Throwable ex) {
10             throw new ApplicationContextException("Unable to start web server", ex);
11         }
12     }

org/springframework/boot/web/servlet/context/ServletWebServerApplicationContext

createWebServer()这个方法的源码我就不贴上了,大家可以自己看一下源码,最后就会看到new tomcat();并进行启动tomcat。启动容器后当然是开始进行初始化。

scss 复制代码
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     }

org/springframework/boot/web/servlet/context/ServletWebServerApplicationContext

现在才到了添加过滤器最关键的部分,这个部分已经基本把上面的三个问题的答案告诉大家了,详情源码如下:

scss 复制代码
 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

一个一个方法分析一下,让大家看个明白到底是怎么回事,为什么这三种方法都可以实现添加过滤器

typescript 复制代码
 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中的原因了,

scss 复制代码
 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

我们再来看一下另一种添加的方法

scss 复制代码
 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

typescript 复制代码
 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方法

scss 复制代码
 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

到这里,过滤器初始化就完成了也把开头的三个问题给大家讲解明白了,剩下的就是过滤请求的过程了,看一下请求过来时的源码处理

scss 复制代码
 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

ini 复制代码
 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数据结构

创建后会进行对请求的过滤,源码:

scss 复制代码
 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

到此创建以及过滤请求的流程分析也就结束了,关于URL过滤的问题,匹配的时候只会识别斜杠/和*不要以为?后面的参数也算到URL过滤里,匹配完路径就完了,然后和拦截器的创建以及拦截分析做一下对比,分析一下两者的区别,如果不知道拦截器的创建以及流程处理可以看一下我的另一篇文章:www.cnblogs.com/guoxiaoyu/p...

相同点:

  1. 都需要交给spring进行管理,虽然filter本身是servlet,但是如果不给spring管理,根本不会添加成功,也不会过滤请求
  2. 都会在请求真正被处理前进行拦截过滤,如果不符合条件会直接返回,不会处理请求
  3. 两者都可以指定执行顺序

差异点:

  1. 过滤器先注册,拦截器后注册
  2. 过滤器先执行,拦截器后执行,拦截器可以在请求执行后继续处理其他事情,过滤器只有一个过滤的方法
  3. 过滤器执行时是基于函数回调,而拦截器执行是直接从数组中获取,一个一个执行,作者没有看到哪里用到了反射,网上好多说是反射,拦截器的三个方法都是从数组中获取然后一个一个调用方法进行的,只有在处理请求的时候才用到了invoke反射
相关推荐
程序猿阿越10 天前
Kafka源码(六)消费者消费
java·后端·源码阅读
zh_xuan16 天前
Android android.util.LruCache源码阅读
android·源码阅读·lrucache
魏思凡19 天前
爆肝一万多字,我准备了寿司 kotlin 协程原理
kotlin·源码阅读
白鲸开源23 天前
一文掌握 Apache SeaTunnel 构建系统与分发基础架构
大数据·开源·源码阅读
Tans51 个月前
Androidx Fragment 源码阅读笔记(下)
android jetpack·源码阅读
Tans51 个月前
Androidx Fragment 源码阅读笔记(上)
android jetpack·源码阅读
Tans51 个月前
Androidx Lifecycle 源码阅读笔记
android·android jetpack·源码阅读
凡小烦1 个月前
LeakCanary源码解析
源码阅读·leakcanary
程序猿阿越2 个月前
Kafka源码(四)发送消息-服务端
java·后端·源码阅读
CYRUS_STUDIO2 个月前
Android 源码如何导入 Android Studio?踩坑与解决方案详解
android·android studio·源码阅读