为什么加了@WebFilter注解,Spring却没有给我自动注入该过滤器?

举报
JavaEdge 发表于 2021/10/17 18:31:36 2021/10/17
【摘要】 在 Spring 编程中,主要配合如下注解构建过滤器:@ServletComponentScan@WebFilter那这看起来只是用上这俩注解就能继续摸鱼了呀。但上了生产后,还是能遇到花式问题:工作不起来顺序不对执行多次等大多因为想当然觉得使用简单,没有上心。还是有必要精通过滤器执行的流程和原理。 @WebFilter 过滤器无法被自动注入为统计接口耗时,实现一个过滤器:该过滤器标记了 @W...

在 Spring 编程中,主要配合如下注解构建过滤器:

  • @ServletComponentScan
  • @WebFilter

那这看起来只是用上这俩注解就能继续摸鱼了呀。但上了生产后,还是能遇到花式问题:

  • 工作不起来
  • 顺序不对
  • 执行多次等

大多因为想当然觉得使用简单,没有上心。还是有必要精通过滤器执行的流程和原理。

@WebFilter 过滤器无法被自动注入

为统计接口耗时,实现一个过滤器:

该过滤器标记了 @WebFilter。所以启动程序加上扫描注解 @ServletComponentScan 让其生效:

然后,提供一个 UserController:

发现应用启动失败

TimeCostFilter 看起来是个普通 Bean啊,为何不能被自动注入?

源码解析

本质上,过滤器被 @WebFilter 修饰后,TimeCostFilter 只会被包装为 FilterRegistrationBean,而 TimeCostFilter 本身只会作为一个 InnerBean 被实例化,这意味着 TimeCostFilter 实例并不会作为 Bean 注册到 Spring 容器。

所以当我们想自动注入 TimeCostFilter 时,就会失败。知道这个结论后,我们可以带着两个问题去理清一些关键的逻辑:

FilterRegistrationBean 是什么?它是如何被定义的

javax.servlet.annotation.WebFilter

所以它不属 Spring,而是 Servlet 规范。
Spring Boot 项目使用它时,Spring Boot 使用了 org.springframework.boot.web.servlet.FilterRegistrationBean 包装 @WebFilter 标记的实例。
实现上来说,即 FilterRegistrationBean#Filter 属性就是 @WebFilter 标记的实例。这点我们可以从之前给出的截图中看出端倪。

定义一个 Filter 类时,我们可能想的是,会自动生成它的实例,然后以 Filter 的名称作为 Bean 名来指向它。
但调试发现,在 Spring Boot 中,Bean 名字确实是对的,只是 Bean 实例其实是 FilterRegistrationBean。

这 FilterRegistrationBean 最早是如何获取的呢?

得追溯到 @WebFilter 注解是如何被处理的。

@WebFilter 是如何工作的

使用 @WebFilter 时,Filter 被加载有两个条件:

  • 声明了 @WebFilter
  • 在能被 @ServletComponentScan 扫到的路径下

直接搜索对 @WebFilter 的使用,可发现 WebFilterHandler 使用了它:

因此,我们选择在 doHandle() 打断点

debug启动,观察调用栈:

可见对 @WebFilter 的处理是在SB启动时,在ServletComponentRegisteringPostProcessor被触发,实现对如下注解的的扫描和处理:

  • @WebFilter
  • @WebListener
  • @WebServlet

WebFilterHandler则负责处理 @WebFilter 的使用:

最后,WebServletHandler 通过父类 ServletComponentHandler 的模版方法模式,处理了所有被 @WebFilter 注解的类:
可见最终注册的 FilterRegistrationBean就是自定义的WebFilter。

看第二个问题:

何时实例化TimeCostFilter

TimeCostFilter 是何时实例化的呢?为什么它没有成为一个普通 Bean?
可在 TimeCostFilter 构造器中加断点,便于快速定位初始化时机:

结合源码,可发现:

  • Tomcat 启动时(onstartUp),才会创建 FilterRegistrationBean
  • FilterRegistrationBean 在被创建时(createBean)会创建 TimeCostFilter 装配自身,而 TimeCostFilter 是通过 ResolveInnerBean 创建的
  • TimeCostFilter 实例最终是一种 InnerBean

所以最终 TimeCostFilter 实例是一种 InnerBean,也就无法自动注入了。

修正

找到根因,就知道如何解决了。

前文解析可知,使用 @WebFilter 修饰过滤器时,TimeCostFilter 类型的 Bean 并没有注册至 Spring 容器,真正注册的是 FilterRegistrationBean。
考虑到还可能存在多个 Filter,可这样修改:

  • 注入 FilterRegistrationBean 类型,而非 TimeCostFilter 类型
  • 注入的名称是包含包名的全限定名,不能直接用 TimeCostFilter,以便存在多个过滤器时,能精确匹配。
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

举报
请填写举报理由
0/200