美文网首页JAVA随笔码农日历
SpringBoot系列教程web篇之过滤器Filter使用指南

SpringBoot系列教程web篇之过滤器Filter使用指南

作者: 一灰灰blog | 来源:发表于2019-10-21 19:21 被阅读0次

    前面一篇博文介绍了在 SpringBoot 中使用 Filter 的两种使用方式,这里介绍另外一种直接将 Filter 当做 Spring 的 Bean 来使用的方式,并且在这种使用方式下,Filter 的优先级可以直接通过@Order注解来指定;最后将从源码的角度分析一下两种不同的使用方式下,为什么@Order注解一个生效,一个不生效

    本篇博文强烈推荐与上一篇关联阅读,可以 get 到更多的知识点: 191016-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南

    I. Filter

    本篇博文的工程执行的环境依然是SpringBoot2+, 项目源码可以在文章最后面 get

    1. 使用姿势

    前面一篇博文,介绍了两种使用姿势,下面简单介绍一下

    WebFilter 注解

    在 Filter 类上添加注解@WebFilter;然后再项目中,显示声明@ServletComponentScan,开启 Servlet 的组件扫描

    @WebFilter
    public class SelfFilter implements Filter {
    }
    
    @ServletComponentScan
    public class SelfAutoConf {
    }
    

    FilterRegistrationBean

    另外一种方式则是直接创建一个 Filter 的注册 Bean,内部持有 Filter 的实例;在 SpringBoot 中,初始化的是 Filter 的包装 Bean 就是这个

    @Bean
    public FilterRegistrationBean<OrderFilter> orderFilter() {
        FilterRegistrationBean<OrderFilter> filter = new FilterRegistrationBean<>();
        filter.setName("orderFilter");
        filter.setFilter(new SelfFilter());
        filter.setOrder(-1);
        return filter;
    }
    

    本篇将介绍另外一种方式,直接将 Filter 当做普通的 Bean 对象来使用,也就是说,我们直接在 Filter 类上添加注解@Component即可,然后 Spring 会将实现 Filter 接口的 Bean 当做过滤器来注册

    而且这种使用姿势下,Filter 的优先级可以通过@Order注解来指定;

    设计一个 case,定义两个 Filter(ReqFilterOrderFilter), 当不指定优先级时,根据名字来,OrderFilter 优先级会更高;我们主动设置下,希望ReqFilter优先级更高

    @Order(1)
    @Component
    public class ReqFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
    
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            System.out.println("req filter");
            chain.doFilter(request, response);
        }
    
        @Override
        public void destroy() {
    
        }
    }
    
    @Order(10)
    @Component
    public class OrderFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
    
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            System.out.println("order filter!");
            chain.doFilter(request, response);
        }
    
        @Override
        public void destroy() {
    
        }
    }
    

    2. 优先级测试

    上面两个 Filter 直接当做了 Bean 来写入,我们写一个简单的 rest 服务来测试一下

    @RestController
    public class IndexRest {
        @GetMapping(path = {"/", "index"})
        public String hello(String name) {
            return "hello " + name;
        }
    }
    
    @SpringBootApplication
    public class Application {
    
        public static void main(String[] args) {
            SpringApplication.run(Application.class);
        }
    
    }
    

    请求之后输出结果如下, ReqFilter 优先执行了

    image

    II. 源码分析

    当我们直接将 Filter 当做 Spring Bean 来使用时,@Order注解来指定 Filter 的优先级没有问题;但是前面一篇博文中演示的@WebFilter注解的方式,则并不会生效

    • 这两种方式的区别是什么?
    • @Order注解到底有什么用,该怎么用

    1. Bean 方式

    首先我们分析一下将 Filter 当做 Spring bean 的使用方式,我们的目标放在 Filter 的注册逻辑上

    第一步将目标放在: org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#selfInitialize

    下面的逻辑中包括了 ServeltContext 的初始化,而我们的 Filter 则可以看成是属于 Servlet 的 Bean

    private void selfInitialize(ServletContext servletContext) throws ServletException {
        prepareWebApplicationContext(servletContext);
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
                beanFactory);
        WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
                getServletContext());
        existingScopes.restore();
        WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
                getServletContext());
        for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
            beans.onStartup(servletContext);
        }
    }
    

    注意上面代码中的 for 循环,在执行getServletContextInitializerBeans()的时候,Filter 就已经注册完毕,所以我们需要再深入进去

    将目标集中在org.springframework.boot.web.servlet.ServletContextInitializerBeans#ServletContextInitializerBeans

    public ServletContextInitializerBeans(ListableBeanFactory beanFactory) {
        this.initializers = new LinkedMultiValueMap<>();
        addServletContextInitializerBeans(beanFactory);
        addAdaptableBeans(beanFactory);
        List<ServletContextInitializer> sortedInitializers = this.initializers.values()
                .stream()
                .flatMap((value) -> value.stream()
                        .sorted(AnnotationAwareOrderComparator.INSTANCE))
                .collect(Collectors.toList());
        this.sortedList = Collections.unmodifiableList(sortedInitializers);
    }
    

    上面有两行代码比较突出,下面单独捞出来了,需要我们重点关注

    addServletContextInitializerBeans(beanFactory);
    addAdaptableBeans(beanFactory);
    

    通过断点进来,发现第一个方法只是注册了dispatcherServletRegistration;接下来重点看第二个

    @SuppressWarnings("unchecked")
    private void addAdaptableBeans(ListableBeanFactory beanFactory) {
        MultipartConfigElement multipartConfig = getMultipartConfig(beanFactory);
        addAsRegistrationBean(beanFactory, Servlet.class,
                new ServletRegistrationBeanAdapter(multipartConfig));
        addAsRegistrationBean(beanFactory, Filter.class,
                new FilterRegistrationBeanAdapter());
        for (Class<?> listenerType : ServletListenerRegistrationBean
                .getSupportedTypes()) {
            addAsRegistrationBean(beanFactory, EventListener.class,
                    (Class<EventListener>) listenerType,
                    new ServletListenerRegistrationBeanAdapter());
        }
    }
    

    从上面调用的方法命名就可以看出,我们的 Filter 注册就在addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());

    image

    上面的截图就比较核心了,在创建FilterRegistrationBean的时候,根据 Filter 的顺序来指定最终的优先级

    然后再回到构造方法中,根据 order 进行排序, 最终确定 Filter 的优先级

    image

    2. WebFilter 方式

    接下来我们看一下 WebFilter 方式为什么不生效,在根据我的项目源码进行测试的时候,请将需要修改一下自定义的 Filter,将类上的@WebFilter注解打开,@Component注解删除,并且打开 Application 类上的ServletComponentScan

    我们这里 debug 的路径和上面的差别不大,重点关注下面ServletContextInitializerBeans的构造方法上面

    当我们深入addServletContextInitializerBeans(beanFactory);这一行进去 debug 的时候,会发现我们自定义的 Filter 是在这里面完成初始化的;而前面的使用方式,则是在addAdapterBeans()方法中初始化的,如下图

    image

    getOrderedBeansOfType(beanFactory, ServletContextInitializer.class)的调用中就返回了我们自定义的 Bean,也就是说我们自定义的 Filter 被认为是ServletContextInitializer的类型了

    然后我们换个目标,看一下 ReqFilter 在注册的时候是怎样的

    关键代码: org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition

    (因为 bean 很多,所以我们可以加上条件断点)

    image

    通过断点调试,可以知道我们的自定义 Filter 是通过WebFilterHandler类扫描注册的, 对这一块管兴趣的可以深入看一下org.springframework.boot.web.servlet.ServletComponentRegisteringPostProcessor#scanPackage

    上面只是声明了 Bean 的注册信息,但是还没有具体的实例化,接下来我们回到前面的进程,看一下 Filter 的实例过程

    private <T> List<Entry<String, T>> getOrderedBeansOfType(
                ListableBeanFactory beanFactory, Class<T> type, Set<?> excludes) {
            Comparator<Entry<String, T>> comparator = (o1,
                    o2) -> AnnotationAwareOrderComparator.INSTANCE.compare(o1.getValue(),
                            o2.getValue());
            String[] names = beanFactory.getBeanNamesForType(type, true, false);
            Map<String, T> map = new LinkedHashMap<>();
            for (String name : names) {
                if (!excludes.contains(name) && !ScopedProxyUtils.isScopedTarget(name)) {
                    T bean = beanFactory.getBean(name, type);
                    if (!excludes.contains(bean)) {
                        map.put(name, bean);
                    }
                }
            }
            List<Entry<String, T>> beans = new ArrayList<>();
            beans.addAll(map.entrySet());
            beans.sort(comparator);
            return beans;
        }
    

    注意我们的 Filter 实例在T bean = beanFactory.getBean(name, type);

    通过这种方式获取的 Filter 实例,并不会将 ReqFilter 类上的 Order 注解的值,来更新FilterRegistrationBean的 order 属性,所以这个注解不会生效

    最后我们再看一下,通过 WebFilter 的方式,容器类不会存在ReqFilter.class类型的 Bean, 这个与前面的方式不同

    image

    III. 小结

    本文主要介绍了另外一种 Filter 的使用姿势,将 Filter 当做普通的 Spring Bean 对象进行注册,这种场景下,可以直接使用@Order注解来指定 Filter 的优先级

    但是,这种方式下,我们的 Filter 的很多基本属性不太好设置,一个方案是参考 SpringBoot 提供的一些 Fitler 的写法,在 Filter 内部来实现相关逻辑

    0. 项目

    web 系列博文

    项目源码

    1. 一灰灰 Blog

    尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激

    下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

    一灰灰blog

    相关文章

      网友评论

        本文标题:SpringBoot系列教程web篇之过滤器Filter使用指南

        本文链接:https://www.haomeiwen.com/subject/kslxvctx.html