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

我们通过向 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,详细原理不再赘述。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot注册 Filter 有两种方式: 1. 使用 @Bean 注解注册 FilterSpring Boot 项目中,可以通过在一个配置类中定义一个 FilterBean 来注册 Filter。具体步骤如下: 1)编写 Filter 类 首先,需要编写一个 Filter 类,该类必须实现 javax.servlet.Filter 接口。 例如: ``` public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { // 初始化方法 } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { // 过滤方法 } @Override public void destroy() { // 销毁方法 } } ``` 2)在配置类中注册 Filter 然后,在一个配置类中使用 @Bean 注解注册 Filter: ``` @Configuration public class MyConfiguration { @Bean public FilterRegistrationBean<MyFilter> myFilter() { FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new MyFilter()); registrationBean.addUrlPatterns("/*"); registrationBean.setName("MyFilter"); registrationBean.setOrder(1); return registrationBean; } } ``` 在上面的代码中,我们使用 FilterRegistrationBean 类来注册 Filter,并设置 Filter 的相关属性,比如 URL 模式、名称、执行顺序等。 2. 使用 @WebFilter 注解注册 Filter 另一种方式是使用 @WebFilter 注解来注册 Filter,该注解需要在 Filter 类上使用。具体步骤如下: 1)编写 Filter 类 同样需要编写一个实现 javax.servlet.Filter 接口的 Filter 类。 例如: ``` @WebFilter(urlPatterns = "/*", filterName = "MyFilter") public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { // 初始化方法 } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { // 过滤方法 } @Override public void destroy() { // 销毁方法 } } ``` 2)启动类添加 @ServletComponentScan 注解 在启动类上添加 @ServletComponentScan 注解,用于启用 Servlet 组件扫描。 例如: ``` @SpringBootApplication @ServletComponentScan public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` 在上面的代码中,@ServletComponentScan 注解告诉 Spring Boot 扫描 @WebFilter、@WebServlet、@WebListener 注解,并将其注册到 Servlet 容器中。 总的来说,使用 @Bean 注解注册 Filter 更加灵活,可以动态地设置 Filter 的属性,而使用 @WebFilter 注解则更加简便,可以一次性完成 Filter注册和属性设置。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值