【WEB系列】过滤器Filter使用指南扩展篇

文章目录
  1. I. Filter
    1. 1. 使用姿势
    2. 2. 优先级测试
  2. II. 源码分析
    1. 1. Bean方式
    2. 2. WebFilter方式
  3. III. 小结
    1. 0. 项目
      1. web系列博文
      2. 项目源码
    2. 1. 一灰灰Blog

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

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

I. Filter

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

1. 使用姿势

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

WebFilter注解

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

1
2
3
4
5
6
7
@WebFilter
public class SelfFilter implements Filter {
}

@ServletComponentScan
public class SelfAutoConf {
}

FilterRegistrationBean

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

1
2
3
4
5
6
7
8
@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优先级更高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@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服务来测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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

1
2
3
4
5
6
7
8
9
10
11
12
13
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

1
2
3
4
5
6
7
8
9
10
11
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);
}

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

1
2
addServletContextInitializerBeans(beanFactory);
addAdaptableBeans(beanFactory);

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

1
2
3
4
5
6
7
8
9
10
11
12
13
@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的实例过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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内部来实现相关逻辑

0. 项目

web系列博文

项目源码

1. 一灰灰Blog

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

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

一灰灰blog


打赏 如果觉得我的文章对您有帮助,请随意打赏。
分享到