别小看Spring过滤器,这些知识点你必须得掌握!(下)

简介: 别小看Spring过滤器,这些知识点你必须得掌握!

WebFilterHandler#doHandle()

image.png

通过 BeanDefinitionBuilder动态构建了FilterRegistrationBean类型BeanDefinition。然而这里并未设置order值,也没设置 @Order 指定值。


至此,也就知道问题根因,所有被**@WebFilter**注解的类,最终都会在此处被包装为FilterRegistrationBean类的BeanDefinition。

虽FilterRegistrationBean也实现了Ordered接口


1.png

但在这并未填充值,因为:

  • 这里所有属性都是从 @WebFilter 对应的属性获取
  • @WebFilter 本身没有指定可以辅助排序的属性

过滤器执行顺序

  • RegistrationBean中order属性的值
  • ServletContextInitializerBeans类成员变量sortedList中元素的顺序
  • ServletWebServerApplicationContext 中selfInitialize()遍历FilterRegistrationBean的顺序
  • addFilterMapBefore()调用的顺序
  • filterMaps内元素的顺序
  • 过滤器的执行顺序


RegistrationBean中order属性的值最终可以决定过滤器的执行顺序。

然而,使用 @WebFilter 时,构建的FilterRegistrationBean并未依据 @Order 的值去设置order属性,所以 @Order 失效。

修正

实现自己的FilterRegistrationBean配置添加过滤器,不再使用 @WebFilter

1.png

由于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构造器:


image.png

image.png

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属性


目录
相关文章
|
2月前
|
负载均衡 监控 Java
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
本文详细介绍了 Spring Cloud Gateway 的核心功能与实践配置。首先讲解了网关模块的创建流程,包括依赖引入(gateway、nacos 服务发现、负载均衡)、端口与服务发现配置,以及路由规则的设置(需注意路径前缀重复与优先级 order)。接着深入解析路由断言,涵盖 After、Before、Path 等 12 种内置断言的参数、作用及配置示例,并说明了自定义断言的实现方法。随后重点阐述过滤器机制,区分路由过滤器(如 AddRequestHeader、RewritePath、RequestRateLimiter 等)与全局过滤器的作用范围与配置方式,提
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
|
1月前
|
缓存 安全 Java
《深入理解Spring》过滤器(Filter)——Web请求的第一道防线
Servlet过滤器是Java Web核心组件,可在请求进入容器时进行预处理与响应后处理,适用于日志、认证、安全、跨域等全局性功能,具有比Spring拦截器更早的执行时机和更广的覆盖范围。
|
SQL Java 测试技术
在Spring boot中 使用JWT和过滤器实现登录认证
在Spring boot中 使用JWT和过滤器实现登录认证
801 0
|
4月前
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
157 0
|
4月前
|
人工智能 安全 Java
Spring Boot 过滤器 拦截器 监听器
本文介绍了Spring Boot中的过滤器、拦截器和监听器的实现与应用。通过Filter接口和FilterRegistrationBean类,开发者可实现对请求和响应的数据过滤;使用HandlerInterceptor接口,可在控制器方法执行前后进行处理;利用各种监听器接口(如ServletRequestListener、HttpSessionListener等),可监听Web应用中的事件并作出响应。文章还提供了多个代码示例,帮助读者理解如何创建和配置这些组件,适用于构建更高效、安全和可控的Spring Boot应用程序。
617 0
|
11月前
|
JavaScript Java Kotlin
深入 Spring Cloud Gateway 过滤器
Spring Cloud Gateway 是新一代微服务网关框架,支持多种过滤器实现。本文详解了 `GlobalFilter`、`GatewayFilter` 和 `AbstractGatewayFilterFactory` 三种过滤器的实现方式及其应用场景,帮助开发者高效利用这些工具进行网关开发。
1600 1
|
Java API Spring
springboot学习六:Spring Boot2.x 过滤器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中过滤器的基础知识和实战项目应用的教程。
394 0
springboot学习六:Spring Boot2.x 过滤器基础入门&实战项目场景实现
|
Java 开发者 Spring
Spring Cloud Gateway 中,过滤器的分类有哪些?
Spring Cloud Gateway 中,过滤器的分类有哪些?
438 3
|
前端开发 Java 数据库连接