zoukankan      html  css  js  c++  java
  • 关于@webFilter使用@Order无效问题

    前言

    SpringBoot系列文章的《第七章:过滤器、监听器、拦截器》中,小技巧中指出,可使用@Order设置过滤器的执行顺序。由于没有自己求证过,看了相关材料后,想当然的写进了文章中,这个进行更正下。

    通过过滤器名称和设置@Order的方法都是不行的。抱歉了,各位。之后在编写文章时,会本着负责且持着大胆猜测小心求证的态度,会对相关事项进行核对的!再次,抱歉,误导了大家

    这里要感谢简书网友:形而上学本尊,指出此错误!再次感谢!

    正确设置排序方式

    《第七章:过滤器、监听器、拦截器》也有指出,利用FilterRegistrationBean可以设置排序顺序。那是否还有其他方式呢。有的,只是这种方案不是很优雅。这里简单说明下。

    先说结论:可以通过过滤器的类名进行约定排序。

    浅谈ServletComponentScan注解的启动方式

    既然遇到了,那就简单分析下使用@WebFilter@ServletComponentScan的启动方式吧。

    首先我们来看下,注解@ServletComponentScan(删除了相关注解):

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(ServletComponentScanRegistrar.class)
    public @interface ServletComponentScan {
    
        @AliasFor("basePackages")
        String[] value() default {};
    
        @AliasFor("value")
        String[] basePackages() default {};
    
        Class<?>[] basePackageClasses() default {};
    
    }
    

    简单来说,此注解就是指定扫描路径的,通过valuebasePackages或者basePackageClasses。主要还是看下ServletComponentScanRegistrar类,这才是关键。

    class ServletComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
    
        private static final String BEAN_NAME = "servletComponentRegisteringPostProcessor";
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            // 获取包路径
            Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
            // 若已注册,则更新,否则新增
            if (registry.containsBeanDefinition(BEAN_NAME)) {
                updatePostProcessor(registry, packagesToScan);
            } else {
                addPostProcessor(registry, packagesToScan);
            }
        }
    
        private void updatePostProcessor(BeanDefinitionRegistry registry, Set<String> packagesToScan) {
            BeanDefinition definition = registry.getBeanDefinition(BEAN_NAME);
            ValueHolder constructorArguments = definition.getConstructorArgumentValues().getGenericArgumentValue(Set.class);
            @SuppressWarnings("unchecked")
            Set<String> mergedPackages = (Set<String>) constructorArguments.getValue();
            mergedPackages.addAll(packagesToScan);
            constructorArguments.setValue(mergedPackages);
        }
    
        private void addPostProcessor(BeanDefinitionRegistry registry, Set<String> packagesToScan) {
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            // 设置类
            beanDefinition.setBeanClass(ServletComponentRegisteringPostProcessor.class);
            // 设置构造函数参数
            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(packagesToScan);
            beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            // 注册
            registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
        }
    
        private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
            // 获取注解ServletComponentScan的属性信息
            AnnotationAttributes attributes = AnnotationAttributes
                    .fromMap(metadata.getAnnotationAttributes(ServletComponentScan.class.getName()));
            // 获取属性basePackages和basePackageClasses
            String[] basePackages = attributes.getStringArray("basePackages");
            Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
            Set<String> packagesToScan = new LinkedHashSet<String>();
            packagesToScan.addAll(Arrays.asList(basePackages));
            // basePackageClasses 最后也是根据basePackageClasses来获取塔对应的包路径
            for (Class<?> basePackageClass : basePackageClasses) {
                packagesToScan.add(ClassUtils.getPackageName(basePackageClass));
            }
            // 默认不填写时,获取的是被注解类所在包路径,所以一般放在启动类上
            if (packagesToScan.isEmpty()) {
                packagesToScan.add(ClassUtils.getPackageName(metadata.getClassName()));
            }
            return packagesToScan;
        }
    }
    

    可以看见,它是一个ImportBeanDefinitionRegistrar的实现类,ImportBeanDefinitionRegistrar可以动态地装载Bean。再来看看ServletComponentRegisteringPostProcessor类,此类是个BeanFactoryPostProcessor,BeanFactory的后置处理器,简单理解就是扩展点吧。启动的时候会调用postProcessBeanFactory方法。
    ServletComponentRegisteringPostProcessor源码就不贴了,简单来说,它的作用就是:扫描被@WebServlet@WebFilter@WebListener的类,最后通过对应的ServletRegistrationBeanFilterRegistrationBeanServletListenerRegistrationBean进行注册。看见这些是不是很熟悉了。

    //部分代码
        static {
            List<ServletComponentHandler> servletComponentHandlers = new ArrayList<ServletComponentHandler>();
            servletComponentHandlers.add(new WebServletHandler());
            servletComponentHandlers.add(new WebFilterHandler());
            servletComponentHandlers.add(new WebListenerHandler());
            HANDLERS = Collections.unmodifiableList(servletComponentHandlers);
        }
    

    关键看这个方法scanPackage:

        private void scanPackage(
                ClassPathScanningCandidateComponentProvider componentProvider,
                String packageToScan) {
            for (BeanDefinition candidate : componentProvider
                    .findCandidateComponents(packageToScan)) {
                if (candidate instanceof ScannedGenericBeanDefinition) {
                    for (ServletComponentHandler handler : HANDLERS) {
                        handler.handle(((ScannedGenericBeanDefinition) candidate),
                                (BeanDefinitionRegistry) this.applicationContext);
                    }
                }
            }
        }
    

    可以看见,通过componentProvider.findCandidateComponents(packageToScan)方法获取到对应的注解类,同时判断是否为以上说的三种,最后调用其doHandle方法完成注册功能。以下是WebFilterHandlerdoHandler方法。

    WebFilterHandler

    现在,我们看看findCandidateComponents方法怎么获取对应注解类的。

    findCandidateComponents

    断点之后,可以看见是AnnotationConfigEmbeddedWebApplicationContext类,

    继续断点进去,最后是使用PathMatchingResourcePatternResolver类进行资源获取的。

    通过递归的方式,获取所有的类:

    最后关键就是这个Arrays.sort(dirContents)了。所以简单来说,可以通过class类名来达到排序效果。但这种方案要限制类名,还是使用FilterRegistrationBean之类的来设置吧。

    总结

    写的可能有点乱也有点水,⊙﹏⊙‖∣。主要还是想纠正下原先的错误,O__O…。知其然知其所以然,还有很长的路要走。没有写里面的细节,只是大致讲解了下。有兴趣的可以自行跟踪看看。

    最后

    目前互联网上很多大佬都有SpringBoot系列教程,如有雷同,请多多包涵了。原创不易,码字不易,还希望大家多多支持。若文中有所错误之处,还望提出,谢谢。

    老生常谈

    • 个人QQ:499452441
    • 微信公众号:lqdevOps

    公众号

    个人博客:http://blog.lqdev.cn

    原文地址:http://blog.lqdev.cn/2018/08/26/%E6%97%A5%E5%B8%B8%E7%A7%AF%E7%B4%AF/correct-webfilter/

  • 相关阅读:
    java实现获取当前年月日 小时 分钟 秒 毫秒
    四种常见的 POST 提交数据方式(application/x-www-form-urlencoded,multipart/form-data,application/json,text/xml)
    Cannot send, channel has already failed:
    Java 枚举(enum) 详解7种常见的用法
    C语言指针详解(经典,非常详细)
    ActiveMQ进阶配置
    Frame size of 257 MB larger than max allowed 100 MB
    SpringJMS解析--监听器
    SpringJMS解析-JmsTemplate
    delphi 修改代码补全的快捷键(由Ctrl+Space 改为 Ctrl + alt + Space)
  • 原文地址:https://www.cnblogs.com/okong/p/correct-webfilter.html
Copyright © 2011-2022 走看看