关于@webFilter使用@Order无效问题

∥☆過路亽.° 提交于 2019-11-30 14:19:56

前言

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方法。

现在,我们看看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/

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!