过滤器执行顺序问题分析

最近碰到了一个关于过滤器的奇怪的问题。

问题描述

项目中定义了若干过滤器,其中一个是解析header参数,并做相应处理。另一个过滤器覆写了getHeader方法,判断参数值中是否有非法字符,如果有则抛出异常。这两个过滤器均通过WebFilter + ServletComponetScan注解方式来声明的。

在写本文之前,我认为WebFilter方式声明的过滤器是按照类名的字母表顺序来执行的,本地执行结果和网上的很多资料都显示是这样。
而这两个过滤器类名按照字母表顺序,覆写getHeader方法的过滤器执行在前,解析header参数的过滤器执行在后,这样当解析header参数调用request.getHeader方法时,就会执行覆写的方法了。

实际上本地执行结果也符合这一认知,但是在k8s集群中执行时问题就来了,从返回结果看并没有进入到覆写方法中,开启远程调试也显示没有进入到覆写方法过滤器中,在过滤器中加上日志后又一切正常。这个bug好像是变成了量子态一样不可观测,找了很久都找不到原因。

但其表现总归是覆写方法的过滤器没有执行到,因此直接更换过滤器注册方式。最后问题不再出现。

根据查询到的资料发现,其实WebFilter声明的过滤器执行是没有明确的顺序的,执行顺序不可控

很多资料都说到了这一点,但是没有给出令人信服的原因。参考资料2中倒是有具体分析,说是通过扫描加载过滤器类文件的时候调用了File类的list方法,该方法不保证文件的有序性。

* <p> There is no guarantee that the name strings in the resulting array
* will appear in any specific order; they are not, in particular,
* guaranteed to appear in alphabetical order.

这样问题就能解释得通了。通过WebFilter注册的过滤器执行顺序不可控,在集群上运行时没有按照预期顺序执行,导致出现了前面描述的问题。不过为什么本地不管重启多少次都始终按照字母表顺序执行,而在集群环境下刚好相反呢?这个表象还是挺奇怪的。

WebFilter为什么不支持排序呢?官方对此有明确解释,就是排序和Filter属于不同的规范,两者不能混为一谈,具体讨论见参考资料3。

过滤器实现方式

在早期过滤器是在web.xml中定义的,过滤器的执行顺序是按照定义的顺序来执行的。现在基本不这么写了,而是通过注解的方式,在spring容器初始化过程中自动装配。一共有三种实现方式:

WebFilter自动注册

首先就是上面说过的通过@WebFilter注解来声明过滤器,然后在启动类上添加@ServletComponetScan注解,该注解会自动扫描@WebServlet、@WebFilter 和 @WebListener 注解的类。WebFilter等注解是在Servlet 3.0引入的。

以这种方式注册的过滤器,以@Order来指定执行顺序是无效的(filterName也无效)。但事实上,以此方式注册的过滤器有一个默认的order值,即FilterRegistrationBean的父类RegistrationBean中定义的order:Integer.MAX_VALUE。因此,通过@WebFilter注册的过滤器其优先级都是一样的。在优先级都一样的情况下,过滤器执行顺序是不固定的,千万不要认为是按字母表顺序执行。

@ServletComponentScan 这个注解仅对内嵌的 tomcat 生效,如果使用单独的 tomcat,这种方式无效。

参数值中,value 和 urlPatterns 不能共存,如果同时指定,通常忽略 value 的取值。

将过滤器注册到FilterRegistrationBean

根据参考资料1我们知道,以WebFilter方式注册的过滤器,实际上还是一种FilterRegistrationBean。因此,可以直接将filter注册成FilterRegistrationBean,并设置order的值。如下所示:

@Configuration
public class FilterConfiguration {

    private static final String URL_PATTERN = "/*";
    private static final String ORDER_VALUE = "1";

    @Bean
    public FilterRegistrationBean<CookieFilter> cookieFilter() {
        FilterRegistrationBean<CookieFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new CookieFilter());
        registrationBean.addUrlPatterns(URL_PATTERN);
        registrationBean.setOrder(ORDER_VALUE);
        return registrationBean;
    }
}

如果不设置order的值,其默认值仍然是Integer.MAX_VALUE

使用@Component注册过滤器

除了上述两种方式外,还可以通过@Component注解注册过滤器。如下所示:

@Order(1)
@Component
public class LoggingFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response,
            final FilterChain filterChain) throws ServletException, IOException {
        ...
     }
}

这种方式注册的过滤器可以通过@Order来指定过滤器执行顺序,默认值为Integer.MAX_VALUE

顺序相同时过滤器优先级

上面三种方式种,除了第一种方式无法指定优先级而采用默认值之外,其他两种方式都可以指定优先级。那如果都采用默认值呢?此时优先级规则如下:

  • @Bean + FilterRegistrationBean
  • @WebFilter + @ServletComponentScan
  • @Component

在这三种过滤器注册方式中,第三种方法无法指定url,也就是说此方式注册的过滤器必定是全局过滤器。第二种方法无法控制顺序,适合没有顺序要求的过滤器。

参考资料

[1].https://blog.csdn.net/Zong_0915/article/details/126747302
[2].https://blog.csdn.net/zy1992As/article/details/129243604
[3].https://github.com/spring-projects/spring-boot/issues/8276

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值