SpringBoot集成Tomcat、DispatcherServlet

通过 SpringBoot 自动配置机制,导入配置类

利用 SpringBoot 自动配置机制,SpringBoot 会导入一个类型为 ServletWebServerFactoryAutoConfiguration 的配置类

ServletWebServerFactoryAutoConfiguration

ServletWebServerFactoryAutoConfigurations 类上存在 @Import 注解,@Import 注解有以下几个特性:

  • 继承 ImportSelector 接口 :会在解析阶段执行 selectImports 方法,方法返回的类名数组,会被解析成 BeanDefinition 对象,即后期会被实例化成Bean
  • 继承 ImportBeanDefinitionRegistrar 接口 :会在解析阶段执行 registerBeanDefinitions 方法,一般会注册 BeanDefinition 对象
  • 配置类

ServletWebServerFactoryAutoConfiguration 类属于第三种情况,我们继续分析

ServletWebServerFactoryConfiguration

spring-boot-starter-web 默认包含依赖 spring-boot-starter-tomcat,因此 Spring 中存在类型为 EmbeddedTomcat 的配置类,EmbeddedJetty、EmbeddedUndertow 因为不满足 @ConditionalOnClass 注解的条件,所以默认情况下 web 容器是 Tomcat

如何修改默认 Web 容器
复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.6.13</version>
    <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>
    <version>2.6.13</version>
</dependency>

此时项目是以 Jetty 为 Web 容器

SpringBoot集成Tomcat

AbstractApplicationContext#refresh

AbstractApplicationContext 的 refresh 方法是一个空壳方法,我们主要看它的实现类 ServletWebServerApplicationContext

ServletWebServerApplicationContext#refresh

getSelfInitializer

其中 getSelfInitializer 方法的返回类型是 ServletContextInitializer, ServletContextInitializer 是一个函数式接口

所以上述代码等价于下列形式:

复制代码
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
    return new ServletContextInitializer() {
        @Override
        public void onStartup(ServletContext servletContext) throws ServletException {
            prepareWebApplicationContext(servletContext);
            registerApplicationScope(servletContext);
            WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
            for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
                beans.onStartup(servletContext);
            }
        }
    };
}
getWebServer
Tomcat#start

经过一系列的方法调用,最终会调用 StandardContext 的 startInternal 方法

TomcatStarter#onStartup

其中 ServletWebServerApplicationContext$lambda 就是我们上文分析的 getSelfInitializer 方法

ServletContextInitializer#onStartup

ServletContextInitializer 的 onStartup 方法,会向容器中注册 Servlet、Filter,DispatcherServlet 就是在这个阶段注册进去的

SpringBoot集成DispatcherServlet

我们在上文中已经分析过了,默认情况下 Spring 中存在的与 web 容器相关的配置类是 EmbeddedTomcat ,该配置类会定义一个类型为 TomcatServletWebServerFactory 的 bean

通过 TomcatServletWebServerFactory 的类图,我们可以知道它是 ErrorPageRegistry 的子类

ErrorPageRegistrarBeanPostProcessor#postProcessBeforeInitialization

其中 getRegistrars 方法会查找 Spring 中类型为 ErrorPageRegistrar 的 bean,在自动配置类 ErrorMvcAutoConfiguration 中就定义了一个类型为 ErrorPageCustomizer 的 bean,该 bean 是 ErrorPageRegistrar 的子类

ErrorPageCustomizer的实例化

errorPageCustomizer 方法会有一个类型为 DispatcherServletPath,根据 Spring 的机制,它会从Spring 中先查找类型为 DispatcherServletPath 的 bean,如果该 bean 还没有被实例化,则优先实例化 bean,作用类似于 @DependsOn

DispatcherServletRegistrationConfiguration#dispatcherServletRegistration

我们再追踪 DispatcherServletPath 的实例化过程,DispatcherServletPath 是一个函数式接口,它有一个实现类 DispatcherServletRegistrationBean

DispatcherServletAutoConfiguration 的内部类 DispatcherServletRegistrationConfiguration 定义了一个返回类型为 DispatcherServletRegistrationBean 的 bean。这个方法有一个类型为 DispatcherServlet 的参数,和 DispatcherServletPath 的特性一致,它会优先实例化类型为 DispatcherServlet 的 bean

DispatcherServletConfiguration#dispatcherServlet

此时 Spring 中就有一个类型为 DispatcherServlet 的 bean

DispatcherServlet#onRefresh

如果 DispatcherServlet 还没有执行 onRefresh 方法,第一次发送请求,就会执行 onRefresh 方法

FrameworkServlet#onRefresh
DispatcherServlet#onRefresh

至此 DispatcherServlet 的实例化以及组件初始化已经完成

相关推荐
Rust研习社2 小时前
组合真的优于继承吗?为什么 Rust 和 Go 都拥抱组合舍弃继承?
后端·rust·编程语言
IT_陈寒2 小时前
JavaScript的闭包把我坑惨了,说好的内存会自动回收呢?
前端·人工智能·后端
CaffeinePro3 小时前
Pydantic深度使用:数据校验、枚举、ORM映射
后端·fastapi
Chenyiax4 小时前
从 Chat 到 Responses:OpenAI API 抽象为什么变了?
后端
MariaH4 小时前
Koa和Express的区别
后端
MariaH4 小时前
Koa框架的使用
后端
luckdewei5 小时前
那个用 passlib 做认证的新同事,上线第一天就把用户密码写进了日志
后端
ping某6 小时前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
JustHappy6 小时前
我汇总了身边朋友的经历才发现,其实第一份实习是最难找的......
前端·后端·面试
uhakadotcom6 小时前
在python 的 工程化架构中 ,什么是 薄包装器层?
后端·面试·github