zoukankan      html  css  js  c++  java
  • 关于SpringBoot实现Servlet的过滤器

    当我们百度搜索“SpringBoot 过滤器 排序”时,有很多文章会讲如何在SpringBoot项目中配置过滤器。大致会分为两种方案:

    • 使用@WebFilter+@Order并配置@ServletComponentScan
    • 使用代码配置的方式编写FilterRegistrationBean,在其中调用setOrder方法来配置顺序。

    实际使用时就会发现使用@WebFilter+@Order的方式虽然可以使过滤器生效,但是却无法使顺序生效。而百度搜索的文章基本上都是抄袭复制的,导致错误的内容广泛传播。
    所以建议在想要学习框架的某种功能时,第一优先级应该去查询官方文档, SpringBoot的官方文档 就有关于过滤器顺序的说明。
    在文档中说明了可以使用@Order或者实现Ordered接口,以及使用FilterRegistrationBeansetOrder方法。那么问题就来了,这官方文档不是说了可以使用@Order注解嘛?难道官方文档出错啦?带着疑问一探究竟

    第一问:SpringBoot是如何进行过滤器注册的?

    首先我们知道,Servlet标准,提供了javax.servlet.Filter接口。在以前的web.xml时候,通过实现接口并将过滤器配置在web.xml中,即可使过滤器生效。
    那么在SpringBoot中,首先注意到这个FilterRegistrationBean,通过查看此类的源代码,可看到注册过滤器的代码:

    @Override
    protected Dynamic addRegistration(String description, ServletContext servletContext) {
    	Filter filter = getFilter();
    	return servletContext.addFilter(getOrDeduceName(filter), filter);
    }
    

      看上去很简单,将我们配置的过滤器实例,直接添加到servletContext中。接着追溯此方法的调用链路,发现是FilterRegistrationBean的父类RegistrationBean中的onStartup方法,而此方法是由接口ServletContextInitializer定义。
      查看接口ServletContextInitializer的备注说明,知道了它是Spring专门用来初始化配置Servlet的一个初始化器。我们还需要注意到RegistrationBean也实现了接口Ordered,这就是FilterRegistrationBean可以用来配置过滤器顺序的原因。但是为什么官方文档说可以自己使用@Order或者实现Ordered接口呢?这个问题的答案需要继续探寻。
      那么我们就可以进行猜测,在某个阶段,由Spring容器触发了ServletContextInitializer的所有实现类的OnStartup方法, 进而就进行了过滤器的注册。

    第二问:为什么文档说可以使用@Order,但是却不生效?

      是不是文档写错了呢?在质疑文档的正确性之前,应该首先想一想,是不是使用的不正确呢?
      我们使用的是@WebFilter+@Order的组合,结果就是顺序根本不起效果。那么在Spring框架下,什么时候见过有使用@Order注解的时候呢?印象就是在配合定义其他SpringBean的时候,那么就试试将@WebFilter改为@Component
      Bingo,@Component+@Order确实可以使过滤器生效并且可以配置顺序了(可以试一试将@Order改为实现Ordered接口,也是生效的)。所以官方文档上说的使用可以使用@Order或者实现Ordered接口就是这种了,但是这样好像就不能配置过滤器参数了!

    第三问:那么@WebFilter是咋回事?

      查看该注解源码,可看到这个注解是定义在javax.servlet.annotation中,看包名猜测是由Servlet规范中定义,并且Servlet容易能够识别。那么在Spring框架下,该注解是如何工作的呢?
      在使用注解时,都需要配合@ServletComponentScan注解配置,才能使@WebFilter生效,那么查看@ServletComponentScan源码,可以跟踪到一个WebFilterHandler类中看到如下处理代码:

    	@Override
    	public void doHandle(Map<String, Object> attributes,
    			ScannedGenericBeanDefinition beanDefinition,
    			BeanDefinitionRegistry registry) {
    		BeanDefinitionBuilder builder = BeanDefinitionBuilder
    				.rootBeanDefinition(FilterRegistrationBean.class);
    		builder.addPropertyValue("asyncSupported", attributes.get("asyncSupported"));
    		builder.addPropertyValue("dispatcherTypes", extractDispatcherTypes(attributes));
    		builder.addPropertyValue("filter", beanDefinition);
    		builder.addPropertyValue("initParameters", extractInitParameters(attributes));
    		String name = determineName(attributes, beanDefinition);
    		builder.addPropertyValue("name", name);
    		builder.addPropertyValue("servletNames", attributes.get("servletNames"));
    		builder.addPropertyValue("urlPatterns", extractUrlPatterns(attributes));
    		registry.registerBeanDefinition(name, builder.getBeanDefinition());
    	}
    

    这里Spring最终也是将@WebFilter注解的过滤器构造为一个FilterRegistrationBean对象。但是,在构造BeanDefinition时,并没有注入order字段值,因此可以知道在@WebFilter注解上使用@Order或者实现Ordered接口是没用的

    第四问:那@Component+@Order又是怎么注册过滤器的?

    那对于这种不知道从何开头的问题,可以使用代码DEBUG的方式,追踪其调用链路。

    简单来说就是写一个无参构造函数,在里面随便写一行代码,然后打个断点,这样就知道这个过滤器是啥时候被Spring实例化的了。

    通过断点的方式,其实就可以很方便的看到调用链路,然后查看源代码即可明白原委。对于这个问题的答案,简单来说,就是这样的流程,容器为Tomcat:
    --> Spring容器启动,将所有(Servlet的)Filter、Servlet、Listener构造为RegistrationBean
    --> 在OnRefresh中new TomcatWebServer
    --> 在创建Tomcat容器的过程中new TomcatStarter
    --> 实例化TomcatStarter时,Spring会将所有Filter、Servlet、Listener的Bean注入
    --> 然后在Tomcat启动完成之后,触发TomcatStarterOnStartup方法
    --> 其中触发ServletContextInitializerOnStartup方法
    --> 回到问题一中注册过滤器的流程

    总结

    SpringBoot配置过滤器并且支持排序的方式有两种:

    • 在过滤器上使用@Component+@Order即可
      但是这种无法配置过滤器参数
    • 代码配置的方式定义FilterRegistrationBean
      这种就很灵活,可以实现过滤器所有配置

    另外,在学习源码的时候,应该是带着问题的学习的。有时并不需要太过注意源码细节,应该理解其抽象含义,等了解到了整体结构之后再一层层的往里拨。

  • 相关阅读:
    Quick Sort
    Binary Search
    trollcave解题
    Openvas简介
    SMB扫描-Server Message Block 协议、nmap
    漏洞基本概念
    防火墙识别、负载均衡识别、waf识别
    Centos7下部署Python项目
    Python-Redis数据类型操作
    MySQL的事务隔离级别
  • 原文地址:https://www.cnblogs.com/bencakes/p/14750958.html
Copyright © 2011-2022 走看看