• 欢迎访问 winrains 的个人网站!
  • 本网站主要从互联网整理和收集了与Java、网络安全、Linux等技术相关的文章,供学习和研究使用。如有侵权,请留言告知,谢谢!

Spring Boot 基础篇–Web(14):过滤器Filter使用指南扩展篇

Spring Boot winrains 来源:一灰灰Blog 12个月前 (11-09) 38次浏览

前面一篇博文介绍了在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优先执行了

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());

上面的截图就比较核心了,在创建FilterRegistrationBean的时候,根据Filter的顺序来指定最终的优先级
然后再回到构造方法中,根据order进行排序, 最终确定Filter的优先级

2. WebFilter方式

接下来我们看一下WebFilter方式为什么不生效,在根据我的项目源码进行测试的时候,请将需要修改一下自定义的Filter,将类上的@WebFilter注解打开,@Component注解删除,并且打开Application类上的ServletComponentScan
我们这里debug的路径和上面的差别不大,重点关注下面ServletContextInitializerBeans的构造方法上面
当我们深入addServletContextInitializerBeans(beanFactory);这一行进去debug的时候,会发现我们自定义的Filter是在这里面完成初始化的;而前面的使用方式,则是在addAdapterBeans()方法中初始化的,如下图

getOrderedBeansOfType(beanFactory, ServletContextInitializer.class)的调用中就返回了我们自定义的Bean,也就是说我们自定义的Filter被认为是ServletContextInitializer的类型了
然后我们换个目标,看一下ReqFilter在注册的时候是怎样的
关键代码: org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition
(因为bean很多,所以我们可以加上条件断点)

通过断点调试,可以知道我们的自定义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, 这个与前面的方式不同

III. 小结

本文主要介绍了另外一种Filter的使用姿势,将Filter当做普通的Spring Bean对象进行注册,这种场景下,可以直接使用@Order注解来指定Filter的优先级
但是,这种方式下,我们的Filter的很多基本属性不太好设置,一个方案是参考SpringBoot提供的一些Fitler的写法,在Filter内部来实现相关逻辑

作者:一灰灰Blog

来源:http://spring.hhui.top/spring-blog/2019/10/18/191018-SpringBoot%E7%B3%BB%E5%88%97%E6%95%99%E7%A8%8Bweb%E7%AF%87%E4%B9%8B%E8%BF%87%E6%BB%A4%E5%99%A8Filter%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97%E6%89%A9%E5%B1%95%E7%AF%87/


版权声明:文末如注明作者和来源,则表示本文系转载,版权为原作者所有 | 本文如有侵权,请及时联系,承诺在收到消息后第一时间删除 | 如转载本文,请注明原文链接。
喜欢 (0)