SpringBoot FilterRegistrationBean 动态向 Web 容器注册 Filter 的原理

不管现实多么惨不忍睹,都要持之以恒地相信,这只是黎明前短暂的黑暗而已。不要惶恐眼前的难关迈不过去,不要担心此刻的付出没有回报,别再花时间等待天降好运。真诚做人,努力做事!你想要的,岁月都会给你。SpringBoot FilterRegistrationBean 动态向 Web 容器注册 Filter 的原理,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

我们通过向 IoC 容器注入 FilterRegistrationBean 的实例,SpringBoot 就自动使其作为一个 Filter 在 Web 容器中生效。

在这里插入图片描述

那这是什么原理呢?

在这里插入图片描述

通过继承关系与属性,我们可以看出 FilterRegistrationBean 本质是对 javax.servlet.Filter 的包装,同时也是 Spring Bean 其继承了 org.springframework.boot.web.servlet.ServletContextInitializer 以便 Spring 监听 Web 容器启动事件,并进行回调。

1. SpringApplication 启动——Web 环境检测

在这里插入图片描述
SpringBoot 启动时通过 deduceFromClasspath 工具类方法来检测当前 jvm 实例的 Classpath 下是否有 javax.servlet.Servlet、ServletContainer、Servlet 等的实现,依此判断当前的 Web 环境。

  • 都没有返回 WebApplicationType.NONE,于是 SpringBoot 就是一个普通 Java 应用,而不是 Web 应用。
  • WebApplicationType.REACTIVE | WebApplicationType.SERVLET 都是 Web 应用。

将 Web 应用类型存入 SpringApplication.webApplicationType,用于后续的处理。

我们当前使用的项目依赖:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
	<exclusions>
		<exclusion>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
		</exclusion>
	</exclusions>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

于是,现在启动的 SpringBoot 便是 jetty-web,是一个 Servlet 应用。

2. SpringApplication 启动——Web 环境 create

在 org.springframework.boot.SpringApplication#run(java.lang.String…) 方法中,触发 Spring refresh 过程。不过在 refresh 之前还需要通过 createApplicationContext() 创建 ApplicationContext,即 Spring 上下文。

Spring 上下文也就与第一步中得到的 webApplicationType 有关,根据值 “SERVLET” 创建 AnnotationConfigServletWebServerApplicationContext 上下文实例。
在这里插入图片描述
AnnotationConfigServletWebServerApplicationContext 是 SpringBoot web 环境上下文,其中也包含了 javax.servlet.ServletContext,也可以看做是一个 Web 容器上下文。

3. SpringApplication 启动——Web 环境 prepare

这里会触发 before refresh 事件,包含 context 的 各种监听器的注册,同时我们使用的 jetty 容器也会监听事件初始化 JettyWebServer 线程池。WebServer 的启动在后续的 refreshed 过程中。

与本文的主线无关,暂不过多介绍。

4. SpringApplication 启动——Web 环境 refresh

也就是 Spring 的 refresh 流程,同时本问的主线内容 FilterRegistrationBean 的动态注册也发生在此 refresh 流程中。

在当前的 jetty-web 环境中,触发的是 ServletWebServerApplicationContext#refresh 不过其直接调用 super.refresh() 未做过多修改,因此我们直接看其父类 AbstractApplicationContext#refresh。
在这里插入图片描述
refresh 流程中顺序定义了各种处理,其中只有 onRefresh() 与本文主线有关,我们接下来关注 onRefresh 方法——ServletWebServerApplicationContext#onRefresh 主要调用 createWebServer 创建 webServer 与配置 servletContext。

WebServer 的创建

委托给了 JettyServletWebServerFactory#getWebServer 来处理,并注册了 JettyServerCustomizer 回调。

ServletWebServerFactory 哪里来?
在这里插入图片描述
通过 SpringBoot 的自动配置注入 spring context。基于条件注解 @Configuration,同样也是根据项目启动的 Classpath 情况进行创建。

如上图,创建了 JettyServletWebServerFactory。最后通过该 Factory#getWebServer 创建的 Jetty WebServer 实例,也就是 JettyEmbeddedWebAppContext。

至此,WebServer 也就创建成功。

WebServer 的启动

等等,我们好像漏掉了一个很重要的东西。WebServer 都创建成功了也没见 Filter 的注册,那 FilterRegistrationBean 什么时候注册到 WebServer呢?

ServletWebServerFactory 创建 WebServer 的方法 getWebServer 有一个可变参数 ServletContextInitializer...
在这里插入图片描述

在 jetty webServer 的构建过程 JettyServletWebServerFactory#getWebServer 中,将 ServletContextInitializer 实例转换为 Configuration 实例存储到了 jetty WebAppContext 中。
在这里插入图片描述

Spring ServletContextInitializer 转 Configuration 以中间形式 ServletContextInitializerConfiguration 类型存在。ServletContextInitializerConfiguration 实现了 jetty Configuration#configure(WebAppContext) 方法,组合了所有 Spring ServletContextInitializer。

当 jetty 启动时回调 ServletContextInitializerConfiguration#configure,同时也需要避免当前类加载器不可见 web 容器派生的类加载器加载的类,于是通过 TCC 机制切换 TCCL 回调了所有的 Spring ServletContextInitializer#onStartup 方法。

而 FilterRegistrationBean 的注册逻辑也就包含在 ServletContextInitializer#onStartup 方法中。

FilterRegistrationBean 的注册逻辑 —— ServletContextInitializer

在这里插入图片描述
ServletWebServerFactory 在创建 WebServe 时,可以传入一个 ServletContextInitializer 类型的参数,用于在 webServer 启动时回调。

从上图可以看出,FilterRegistrationBean 实现了 ServletContextInitializer ,也就是说我们在文章开头向 IoC 中注册的 Filter 其具备被 web 容器启动时回调的功能

ServletContextInitializer 的实现包含在了 ServletWebServerApplicationContext#selfInitialize 方法中。也就是说 jetty web 在启动时触发的 org.eclipse.jetty.webapp.AbstractConfiguration#configure 回调其实也就是在执行下图中的 selfInitialize 方法。非常重要的部分,代表 web 容器与 Spring 容器的关联。

在这里插入图片描述
接下来主要关注 selfInitialize 方法实现。

在这里插入图片描述

getServletContextInitializerBeans 方法将容器中所有的 ServletContextInitializer 类型的 Bean 收集到集合,通过隐式返回迭代器进行遍历。

在这里插入图片描述
详细的 ServletContextInitializer bean 收集逻辑:
在这里插入图片描述

debug 可以看到我们文章开头动态注册的 Bean :

在这里插入图片描述

遍历集合实现依次执行回调方法 ServletContextInitializer#onStartup,同时也就触发了将 Filter Bean 动态注册到 web 容器的回调:

在这里插入图片描述

至此,本文完整地详述了 Spring Filter Bean —— FilterRegistrationBean 到 Web Filter 的转换实现逻辑。

SpringWebMVC 应用部署到传统 servlet 容器,由 web 先启动是利用 SPI 扫描得到 ServletContainerInitializer 实例, servlet 容器启动时执行其回调从而启动 Spring。传统 servlet 容器不同于本文的嵌入式 servlet 容器,由 Springboot 先启动从容器中收集 ServletContextInitializer 注册给 servlet 容器。——传统的是容器主动扫描,嵌入式的是被动注入给容器。

SpringBoot 支持部署到传统 servlet 容器,利用 ServletContainerInitializer 的 SPI 实现。

也就是说,其实现原理是在打包时生成 META-INF/services/javax.servlet.ServletContainerInitializer 在其中指定 org.springframework.web.SpringServletContainerInitializer,配合 @HandlesTypes 利用 SPI 实现通过 servlet 容器启动引发 SpringBoot web 应用启动。

参考web 容器 SCI 机制 javax.servlet.ServletContainterInitializer & Spring org.springframework.web.SpringServletContainerInitializer,详细原理不再赘述。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/180264.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!