我们通过向 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