SpringBoot整合多个Filter/Interceptor以及一些区别

SpringBoot整合多个Filter/Interceptor以及一些区别

捡破烂的诗人 575 2022-12-17
  • 联系方式:1761430646@qq.com
  • 编写时间:2022年12月17日11:28:31
  • 博客地址:www.zeroeden.cn
  • 菜狗摸索,有误勿喷,烦请联系

1. 前言

  • 只做如何使用,不做为什么会这样(菜狗水平,目前顶不住)

  • 默认搭建好了SpringBoot环境

    /**
     * @author: Zero
     * @time: 2022/12/9
     * @description:
     */
    
    @SpringBootApplication
    public class TestApplication {
        public static void main(String[] args) {
            SpringApplication.run(TestApplication.class, args);
        }
    }
    
    
    /**
     * @author: Zero
     * @time: 2022/12/9
     * @description: 控制层
     */
    
    @RestController
    public class TestController {
    
        @GetMapping("/test")
        public String test() {
            System.out.println("====这是控制层test()方法====");
            return "success";
        }
    
        @GetMapping("/testA")
        public String testA() {
            System.out.println("====这是控制层testA()方法====");
            return "success";
        }
    
        @GetMapping("/testA/A")
        public String testAA() {
            System.out.println("====这是控制层testA/A()方法====");
            return "success";
        }
    
        @GetMapping("/testB")
        public String testB() {
            System.out.println("====这是控制层testB()方法====");
            return "success";
        }
    
        @GetMapping("/testB/B")
        public String testBB() {
            System.out.println("====这是控制层testBB()方法====");
            return "success";
        }
        @GetMapping("/my")
        public String my() {
            System.out.println("====my()方法====");
            return "success";
        }
        @GetMapping("/my.html")
        public String myHtml() {
            System.out.println("====myHtml()方法====");
            return "success";
        }
        @GetMapping("/my.json")
        public String myJson() {
            System.out.println("====myJson()方法====");
            return "success";
        }
    }
    

2. Filter

2.1 SpringBoot 整合多个 Filter

  • 其中一个Filter实现代码模板如下

    /**
     * @author: Zero
     * @time: 2022/11/28
     * @description:
     */
    
    public class FilterA1 implements Filter {
    
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("--------[FilterA1]过滤器:  init() -- 执行初始化方法--------");
        }
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    
            System.out.println("--------[FilterA1]过滤器:  doFilter() -- 放行前 --------");
            filterChain.doFilter(servletRequest, servletResponse);
            System.out.println("--------[FilterA1]过滤器:  doFilter() -- 放行后 --------");
        }
    
        @Override
        public void destroy() {
            System.out.println("--------[FilterA1]过滤器:  destroy() -- 执行销毁方法--------");
        }
    }
    
  • 按照上述代码模板,同时Copy多个,为其起不同类名并改动输出的Filter名称即可,如下

2.1.1 使用配置类

  • 通过在配置类中为各Filter注册成Bean并做相关过滤器配置即可

  • 配置如下

    /**
     * @author: Zero
     * @time: 2022/12/9
     * @description: Filter 配置类
     */
    
    @Configuration
    public class FilterConfiguration  {
    
        @Bean
        public FilterA1 createFilterA1(){
            return new FilterA1();
        }
        @Bean
        public FilterA2 createFilterA2(){
            return new FilterA2();
        }
        @Bean
        public FilterB1 createFilterB1(){
            return new FilterB1();
        }
        @Bean
        public FilterC1 createFilterC1(){
            return new FilterC1();
        }
    
        @Bean
        public FilterRegistrationBean<FilterA1> filterA1(){
            FilterRegistrationBean<FilterA1> filterRegistrationBean = new FilterRegistrationBean<>();
            filterRegistrationBean.setFilter(createFilterA1()); // 设置过滤器
            filterRegistrationBean.addUrlPatterns("/*");        // 增加拦截路径
            return filterRegistrationBean;
        }
        @Bean
        public FilterRegistrationBean<FilterA2> filterA2(){
            FilterRegistrationBean<FilterA2> filterRegistrationBean = new FilterRegistrationBean<>();
            filterRegistrationBean.setFilter(createFilterA2()); // 设置过滤器
            filterRegistrationBean.addUrlPatterns("/*");        // 增加拦截路径
            return filterRegistrationBean;
        }
        @Bean
        public FilterRegistrationBean<FilterB1> filterB1(){
            FilterRegistrationBean<FilterB1> filterRegistrationBean = new FilterRegistrationBean<>();
            filterRegistrationBean.setFilter(createFilterB1()); // 设置过滤器
            filterRegistrationBean.addUrlPatterns("/*");        // 增加拦截路径
            return filterRegistrationBean;
        }
        @Bean
        public FilterRegistrationBean<FilterC1> filterC1(){
            FilterRegistrationBean<FilterC1> filterRegistrationBean = new FilterRegistrationBean<>();
            filterRegistrationBean.setFilter(createFilterC1()); // 设置过滤器
            filterRegistrationBean.addUrlPatterns("/*");        // 增加拦截路径
            return filterRegistrationBean;
        }
        
    }
    
  • 启动程序,访问/test路径结果如下

2.1.2 @Component

  • 直接在每个Filter中加入注解@Component,将其对应Filter注入到容器中即可

  • 启动程序,访问/test路径结果如下

2.1.3 @WebFilter + @ServletComponentScan

  • 在每个Filter上加入注解WebFilter(标明这是个过滤器),以及在启动类上加入注解@ServletComponent,配置Filter的扫描路径

  • 启动程序,访问/test路径结果如下

  • 注意:

    • @WebFilterServlet中的注解,如下图

    • 它的作用就是去标明这个类是Filter

    • 不能说只加@WebFilter,而不在启动类上加入@ServletComponentScan注解标明Servlet组件的扫描路径,否则的话Filter是不生效的

    • 切记,切记,这两个注解得搭配使用才行

2.2 SpringBoot 整合多个 Filter 为其设置过滤顺序

  • 注意:这是在SpringBoot中整合多个Filter的基础上去设置每个Filter的过滤顺序,也就是此章节是建立在【2.1】的整合基础下,整合多个Filter时有多种方式,这里就有多少种设置过滤顺序的方式

2.2.1 在配置类中配置

2.2.1.1 演示

  1. 在章节【2.1.1】中,当我们配置多个过滤器时,可以看到在配置类总我们的代码从上到下是按照FilterA1FilterA2FilterB1FilterC1的顺序配置的

  2. 然后访问接口时,从控制台输出中我们也可以看到其过滤顺序是刚好也是FilterA1FilterA2FilterB1FilterC1

  3. 所以现在可以轻易得出个小结:在使用配置类的方式去配置Filter时,默认情况下是按照Filter的配置顺序去控制Filter的过滤顺序的

  4. 现在我们可以在配置类中调整下配置Filter时的顺序做下测试,看小结是否准确,比如说把FilterA2的配置调到最后,如下图

  5. 现在访问接口时,其图如下,可以看到过滤顺序其结果为FilterA1FilterB1FilterC1FilterA2,正如小结所说,证明小结是准确的(默认情况下是按照Filter的配置方法顺序去控制Filter的过滤顺序的

  6. 但是在使用配置类的方式时,就真的只能通过去调整配置Filter方法的顺序去控制Filter的过滤顺序么

  7. 答案不是的

  8. 在配置每个Filter时,其FilterRegistrationBean对象内部有一个属性order,其作用是配置当前Filter的过滤顺序,值越小,顺序排在越前,且默认值为2147483647(int的最大值)

    • 注意:这个order属性是FilterRegistrationBean类通过间接继承父类RegistrationBean得来的

  9. 现在我们为每个Filter设置其order值来控制器过滤顺序,配置如下

  10. 可以看到其order值从大到小为FilterA1FilterA2FilterB1FilterC1,按照order值越小,过滤排在越前的规则,也就是实际上过滤顺序该为FilterC1,FilterB1,FilterA2FIlterA1

  11. 访问/test接口,其结果如下

2.2.1.2 章节小结

  • 在使用配置类这种方式去配置Filter
  • 是通过设置FilterRegistrationBean对象中order值来控制过滤顺序的
  • order值越小,过滤顺序排在越前,order的默认值为int的最大值,也即是2147483647
  • 对于每个Filter,如果说设置了order值,就按照order值的大小来控制过滤顺序
  • 如果没有,order就为默认值,当多个Filterorder值相等时或者都没有设置Order值时,默认会按照在配置类中配置Filter时的方法顺序从上到下来顺序排序过滤顺序

2.2.2 @Component + @Order

2.2.2.1 演示

  1. 在章节【2.1.2】中,我们可以通过直接在每个Filter上加入注解@Component去配置过滤器

  2. 先讲个小结:这时候默认的过滤顺序其实是通过比较类名来决定的

  3. 在章节【2.1.2】中,我们可以看到这4个的类名比较顺序如下

  4. 启动程序,访问/test接口,其过滤顺序也正好是FilterA1FilterA2FilterB1FilterC1

  5. 现在在此基础上,我们再增加两个Filter,分别是FilterA0FilterB2

  6. 再次启动程序,访问/test接口,其过滤顺序也正好是FilterA0,FilterA1FilterA2FilterB1FilterB2,FilterC1

  7. 可见上述小结是正确的(使用@Component这种方式,默认情况下是通过比较类名来决定过滤顺序的

  8. 而对于这种方式,在每个Filter上再加入注解@Order并设置其值,即可配置当前Filter的过滤顺序

  9. @Order值正如上个章节【2.2.1】一样,是配置过滤顺序的(只不过换成了使用注解来设置的方式),过滤顺序规则还是依旧,值越小,过滤顺序越前,默认值为int的最大值,即为2147483647

  10. 现在我们可以在每个Filter上,加入注解@Order,并配置其值,其配置如下

  11. 启动程序,访问/test接口,其过滤顺序结果如下

  12. 可以看到过滤顺序如我们所愿

2.2.2.2 章节小结

  • 在使用@Component这种方式去配置Filter
  • 我们可以在对应Filter上通过加入注解@Order设置值来设置当前过滤器过滤顺序
  • 其值设置的越小,过滤顺序排在越前,默认值为int的最大值,也即是2147483647
  • 对于每个Filter,会按照注解@Order中的值来控制过滤顺序
  • 如果说没有配置此注解,或者说配置的值相等,则会默认通过比较类名(越小过滤顺序越前)来决定过滤顺序

2.2.3 @WebFilter + @ServletComponentScan

  • 对于这种配置方式,默认还是按照比较类名的方式来决定过滤顺序(越小过滤顺序越前)

  • 但目前来说,比较遗憾的是这种配置方式没有额外的方法可以配置其过滤顺序

  • 也就是说,SpringBoot使用@WebFilter+ServletComponentScan这种配置Filter的方式,就只能通过比较Filter类名的方式进行设置过滤顺序

  • 原本想着默认还是按照比较Filter类名来设置过滤顺序的,那么我们在注解@WebFilter上,去设置其filterName的值来看能不能通过这种方式控制过滤顺序,很遗憾,不能,其实验如下

  • 后面又突发奇想,想着@WebFilter+ServletConponentScan,看能不能通过加上注解@Order的方式去控制过滤顺序,然而,还是不行,其实验如下

2.3 SpringBoot 整合 Filter 设置过滤规则

  • 先说个结论:就是@Component这种SpringBoot整合Filter的方式,是设置不了过滤规则的(至少目前俺是找不到有啥方式可以配置),而另外两种方式就行

  • 说下另外两种方式如何配置:

    • 使用配置类配置

      • 在配置Filter的方法中,FilterRegistrationBean类中可以调用addUrlPatterns方法(可变长参数)去增加过滤路径,也可以调用setUrlPatterns方法设置过滤路径

      • 分别访问/test,/testA,/testA/A/testB/my接口,其实验结果如下

      • 同时在增加过滤匹配规则时支持后缀匹配(也就代表过滤匹配静态资源)

      • 分别访问接口/test,/testA/my.json,my.html接口,其实验结果如下

    • @WebFilter + ServletComponentScan

      • @WebFilter注解中,有一个名为urlPatterns的字符串数组属性,即是用来设置过滤匹配规则的

      • 这里就不再演示了,它也支持后缀匹配

2.4 SpringBoot 整合 Filter 其他注意事项

2.4.1 过滤规则 /* 和 /** 是不一样的

  • 直接先说结论然后验证

  • SpringBoot整合Filter中,设置过滤规则时

    • /*表示匹配URL/为前缀的任意请求(如果是/api/*,就代表匹配URL/api为前缀的任意请求)

      1. 设置过滤规则为/*,且准备几个接口

      2. 分别访问接口/test,/testA/testA/A/my.html/my.json

      3. 可以看到所有请求都匹配上了,也就证明了/*是过滤所有URL/为前缀的请求

    • 在设置Filter的过滤规则时,是没有/**这种写法

      1. 按照上述例子,改过滤规则为/**,其他不变

      2. 分别访问接口/test,/testA/testA/A/my.html/my.json

      3. 可以看到,过滤规则/**根本不生效

2.4.2 使用配置类方式时在某种特殊情况下是无法在过滤器中注入Bean的

  • 如标题所言,这种特殊情况就是方法中使用FilterRegistrationBean对象设置Filter时,如果此Filter是直接new出来的话,那么此时是无法在此过滤器中注入Bean
  1. 如下演示这种配置方式情况,配置如下图

  2. 启动程序,访问任意接口,会直接报空指针异常

  3. 至于为什么

  4. 一开始我是觉得new出来的Filter实际上是不受IOC容器管理的(只是封装后的FilterRegistrationBean有而已),所以注入不了其他Bean

  5. 后面看到别人分析,说SpringMVC的启动是在DispatchServlet里面做的,而它是在ListenerFilter之后执行,如果我们想在ListenerFilter里面@Autowired某个Bean,肯定是不行的,因为Filter初始化的时候,此时Bean还没有初始化,所以无法自动装配

  6. 菜狗表示没看过源码,暂时无法验证

3. Interceptor

3.1 SpringBoot 整合多个 Interceptor

  1. 其中一个Interceptor实现代码模板如下

    /**
     * @author: Zero
     * @time: 2022/12/4
     * @description: 拦截器
     */
    
    //@Component
    public class InterceptorA1 implements HandlerInterceptor {
    
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("--------拦截器[InterceptorA1]: preHandle() -- 拦截前的处理--------");
            return HandlerInterceptor.super.preHandle(request, response, handler);
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("--------拦截器[InterceptorA1]: postHandle() -- 拦截处理--------");
    
            HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("--------拦截器[InterceptorA1]: afterCompletion() --  --------");
            HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
        }
    }
    
  2. 按照上述代码模板,同时COPY多个,为其起不同类名并改动输出的Interceptor名称即可,如下

  3. 接下就是配置的事了

  4. 由于InterceptorSpring框架自带的,所以说直接通过实现了WebMvcConfigurer接口配置类,重写addInterceptors(InterceptorRegistry registry)方法直接配置就好

  5. 配置如下图所示

  6. 启动程序,访问接口/test,结果如下图所示

  7. 可以见到多个Interceptor生效,整合成功

3.2 SpringBoot 整合多个 Interceptor 为其设置拦截顺序

  • 在默认情况下,Interceptor的拦截顺序是与在addInterceptors()方法内部的注册顺序有关的,Interceptor注册的越早,其拦截顺序越前
  1. 下面我们可以调整下在刚刚的addInterceptors()方法内部注册的Interceptor顺序,如下图所示

  2. 启动程序,访问接口/test,结果如下图所示

  3. 可以看到,拦截顺序正如我们配置Interceptor的顺序一致,结论正确

  4. 但是正如同配置Filter那样,可以通过设置order值来决定过滤顺序,Interceptor也同样可以做

  5. SpringBoot使用InterceptorRegistration类来统一封装Interceptor,其内部含有属性order用来设置拦截顺序,order值越小,拦截顺序排在越前(默认值为0)

  6. 现在我们可以为各个Interceptor设置order值做下测试,配置如下

  7. 启动程序,访问接口/test,结果如下图所示

  8. 可以看到其拦截顺序正如我们配置的那样,按照order值从小到大排序

  • 小结

    • 各个Interceptor默认是按照order值来决定拦截顺序的
    • order值默认为0
    • 如果没有配置order值或者order值相同的多个Interceptor
    • 按照在实现了接口WebMvcConfigurer的配置类的addInterceptors方法中注册Interceptor的顺序排列

3.3 SpringBoot 整合 Interceptor 设置拦截规则

  • 正如SpringBoot整合Interceptor的方式一样,设置拦截规则也是在addInterceptors()方法内部实现,直接调用addPathPatterns()方法(可变长参数)即可添加拦截规则
  1. 下面做下演示配置

  2. 启动程序,分别访问接口/test,/testA/my.html,实验结果如下图

  3. 可以看到/test接口成功被拦截到

  4. 要特别说明的是,Interceptor的拦截规则是不支持后缀匹配的

  5. 比如我们配置拦截规则为*.html

  6. 启动程序,访问接口/my.html,实验结果如下图

  7. 可以看到我们想要的拦截以.html为结尾的请求是失效的,也就证明了Intercetptor的拦截规则是不支持后缀匹配的

3.4 拦截规则 /* 和 /** 是不一样的

  • 直接先说结论然后验证

  • SpringBoot整合Interceptor中,设置拦截规则时

    • /*表示匹配URL/开头,后面最多只能有一级的请求比如说设置拦截规则为/api/*,则能拦截URL为/api,/api/test的请求,而/api/test/hello(后面跟了两级)是无法拦截的

      1. 设置拦截规则为/testA/*,且准备几个接口

      2. 启动程序,分别访问接口/test,/testA,/testA/A,/testA/A/A,实验结果如下

      3. 可以看到,只有访问接口/testA/A时拦截器才拦截到,而/testA/A/A无效,也就证明结论是正确的(/*后面最多只能加一级路径,否则不生效)

      4. 特别说明的是,请求路径/testA/testA/是不同的,前者是根路径/后面只有一级路径,而后者根路径/后面跟的是二级路径,只是二级路径那里为空字符而已

    • /**代表匹配URL/开头的任意请求(后面无路径时,/可以省略,比如说拦截规则为/api/**,实际上请求/api也会拦截到)–这里就不再演示效果了

  • 假设以访问静态资源为例,简单来说:

    • /*是拦截所有的文件夹,但是不包含子文件夹–后面只有一级
    • /**是拦截所有的文件夹以及里面包含的子文件夹–后面可以包含多级

4. 总结

4.1 Filter

  • Filter配置方式区别:

  • 使用FIlter注意事项

  • Filter设置过滤规则注意事项

  • 总结

    1. 总的来说,使用配置类的方式最好,功能最全,统一集中管理各个Filter,后面维护起来也方便
    2. 如果想要使用比较简单,又不用配置拦截路径的话,使用@Component+@Order这种配置方式,如果不用设置过滤顺序的话,@Order还能忽略掉
    3. @WebFilter+@ServletComponentScan个人感觉用的比较少,感觉像是@SpringBoot为了兼容@WebFilter弄出来的
    4. 所以总得来说,推荐使用配置类的方式,并且在过滤器中,离业务层接口比较远,应该做的是一些无关业务的操作,比如说字符集编码设置等等,同时,如果项目是前后端不分离的话,并且如果有这个需求的话,过滤器可以对静态资源进行过滤

4.2 Interceptor

  • Interceptor配置拦截规则注意事项

  • 总结

    1. Interceptor离业务层比较近,通常用来做一些有关业务的事情,比如说登录状态,权限认证等等
    2. 而且Interceptor个人感觉主要是针对接口拦截,也就是不理静态资源的访问(如果项目是前后端不分离的话)
    3. 同时,要特别注意拦截规则/*/**的区别,这是与Filter的配置过滤规则走的是不一样的流程

5. 引用


# SpringBoot # 过滤器 # Filter # Interceptor # 拦截器 # 区别 # 联系 # 拦截规则