WebFilterHandler#doHandle()
通过 BeanDefinitionBuilder动态构建了FilterRegistrationBean类型BeanDefinition。然而这里并未设置order值,也没设置 @Order 指定值。
至此,也就知道问题根因,所有被**@WebFilter**注解的类,最终都会在此处被包装为FilterRegistrationBean类的BeanDefinition。
虽FilterRegistrationBean也实现了Ordered接口
但在这并未填充值,因为:
- 这里所有属性都是从 @WebFilter 对应的属性获取
- 但 @WebFilter 本身没有指定可以辅助排序的属性
过滤器执行顺序
- RegistrationBean中order属性的值
- ServletContextInitializerBeans类成员变量sortedList中元素的顺序
- ServletWebServerApplicationContext 中selfInitialize()遍历FilterRegistrationBean的顺序
- addFilterMapBefore()调用的顺序
- filterMaps内元素的顺序
- 过滤器的执行顺序
RegistrationBean中order属性的值最终可以决定过滤器的执行顺序。
然而,使用 @WebFilter 时,构建的FilterRegistrationBean并未依据 @Order 的值去设置order属性,所以 @Order 失效。
修正
实现自己的FilterRegistrationBean配置添加过滤器,不再使用 @WebFilter :
由于WebFilterHandler#doHandle()虽构建FilterRegistrationBean类型BeanDefinition,但未设置order值。
所以,考虑直接手动实例化FilterRegistrationBean实例且设置其setOrder()。
切记去掉AuthFilter和TimeCostFilter类中的**@WebFilter** 即可。
2 竟然重复执行了过滤器
解决排序问题,可能有人就想了是不是有其他解决方案?
比如在两个过滤器中使用 @Component,让@Order生效?
代码如下。
AuthFilter:
@WebFilter @Slf4j @Order(2) @Component public class AuthFilter implements Filter { @SneakyThrows @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain){ if(isPassAuth()){ System.out.println("通过授权"); chain.doFilter(request, response); }else{ System.out.println("未通过授权"); ((HttpServletResponse)response).sendError(401); } } private boolean isPassAuth() throws InterruptedException { System.out.println("执行检查权限"); Thread.sleep(1000); return true; } }
TimeCostFilter类如下:
@WebFilter @Slf4j @Order(1) @Component public class TimeCostFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("#开始计算接口耗时"); long start = System.currentTimeMillis(); chain.doFilter(request, response); long end = System.currentTimeMillis(); long time = end - start; System.out.println("#执行时间(ms):" + time); } }
最终执行结果如下:
#开始计算接口耗时 执行检查权限 通过授权 执行检查权限 通过授权 #开始计算接口耗时 ......用户注册成功 #执行时间(ms):73 #执行时间(ms):2075
更改 AuthFilter 类中的Order值为0,继续测试,得到结果如下:
执行检查权限 通过授权 #开始计算接口耗时 执行检查权限 通过授权 #开始计算接口耗时 ......用户注册成功 #执行时间(ms):96 #执行时间(ms):1100
看来控制Order值可以调整Filter执行顺序了,但过滤器本身却被执行2次,why?
源码解析
被@WebFilter的过滤器,会在WebServletHandler类中被重新包装为FilterRegistrationBean类的BeanDefinition,而非Filter类型。
而自定义过滤器增加 @Component 时,怀疑Spring会根据当前类再次包装一个新过滤器,因而doFIlter()被执行两次。
ServletContextInitializerBeans构造器:
addAdaptableBeans()
实例化并注册了所有实现Servlet、Filter及EventListener接口的类,重点看Filter实现类,然后再逐一包装为FilterRegistrationBean。
于是,可知Spring能直接实例化FilterRegistrationBean类型过滤器的原因:
WebFilterHandler相关类通过扫描 @WebFilter,动态构建了FilterRegistrationBean类型的BeanDefinition,并注册到Spring;
或我们自己使用 @Bean 显式实例化FilterRegistrationBean并注册到Spring,如案例1中的解决方案。
但Filter类型的过滤器如何才能被Spring直接实例化呢?
任何通过 @Component 修饰的的类,都可自动注册到Spring,被Spring直接实例化。
调用了addAsRegistrationBean(),其beanType为Filter.class:
protected void addAdaptableBeans(ListableBeanFactory beanFactory) { // ... addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter()); // ... }
继续查看最终调用到的方法addAsRegistrationBean():
private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type, Class<B> beanType, RegistrationBeanAdapter<T> adapter) { // 创建所有 Filter 子类的实例,即所有实现Filter接口且被@Component修饰的类 List<Map.Entry<String, B>> entries = getOrderedBeansOfType(beanFactory, beanType, this.seen); // 依次遍历这些Filter类实例 for (Entry<String, B> entry : entries) { String beanName = entry.getKey(); B bean = entry.getValue(); if (this.seen.add(bean)) { // 通过RegistrationBeanAdapter将这些类包装为RegistrationBean RegistrationBean registration = adapter.createRegistrationBean(beanName, bean, entries.size()); // 获取Filter类实例的Order值 int order = getOrder(bean); // 设置到包装类 RegistrationBean registration.setOrder(order); // 将RegistrationBean添加到this.initializers this.initializers.add(type, registration); if (logger.isTraceEnabled()) { logger.trace("Created " + type.getSimpleName() + " initializer for bean '" + beanName + "'; order=" + order + ", resource=" + getResourceDescription(beanName, beanFactory)); } } } }
当过滤器同时被 @WebFilter、@Component 修饰时,会导致两个FilterRegistrationBean实例产生:
- addServletContextInitializerBeans()
- addAdaptableBeans()
最终都会创建FilterRegistrationBean的实例,但不同的是:
- @WebFilter 会让addServletContextInitializerBeans()实例化,并注册所有动态生成的FilterRegistrationBean类型的过滤器
- @Component 会让addAdaptableBeans()实例化所有实现Filter接口的类,然后再逐一包装为FilterRegistrationBean类型的过滤器。
修正
参考案例1的问题修正部分。
也可去掉@WebFilter保留@Component:
//@WebFilter @Slf4j @Order(1) @Component public class TimeCostFilter implements Filter { //省略非关键代码 }
总结
@WebFilter和@Component的相同点是:
- 它们最终都被包装并实例化成为了FilterRegistrationBean;
它们最终都是在 ServletContextInitializerBeans的构造器中开始被实例化。
@WebFilter和@Component的不同点:
- 被@WebFilter修饰的过滤器会被提前在BeanFactoryPostProcessors扩展点包装成FilterRegistrationBean类型的BeanDefinition,然后在ServletContextInitializerBeans.addServletContextInitializerBeans() 进行实例化
- @Component修饰的过滤器类,在ServletContextInitializerBeans.addAdaptableBeans() 中被实例化成Filter类型后,再包装为RegistrationBean类型
- 被@WebFilter修饰的过滤器不会注入Order属性
- 被@Component修饰的过滤器会在ServletContextInitializerBeans.addAdaptableBeans() 中注入Order属性