对于一个 SpringBoot web 工程来说,一个主要的依赖就是有 spring-boot-starter-web 这个 starter ,spring-boot-starter-web 模块在 spring boot 中并没有代码存在,只是在 pom.xml 中携带了一些其他的web相关的依赖,包括 webmvc、tomcat 等等.这些 提供了一个 web 应用的运行环境。
在 spring-boot-autoconfigure 模块中,有处理关于 WebServer 的自动配置类 ServletWebServerFactoryAutoConfiguration
代码片段如下:
@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration
两个 Condition 表示当前运行环境是基于 servlet 标准规范的 web 服务:
ConditionalOnClass(ServletRequest.class) : 表示当前必须有 servlet-api 依赖存在
ConditionalOnWebApplication(type = Type.SERVLET) :仅基于servlet的Web应用程序
@EnableConfigurationProperties(ServerProperties.class):ServerProperties 配置中包括了常见的 server.port 等配置属性。
通过 @Import 导入嵌入式容器相关的自动配置类,有 EmbeddedTomcat、EmbeddedJetty 和EmbeddedUndertow。
综合来看,ServletWebServerFactoryAutoConfiguration 自动配置类中主要做了以下几件事情:
导入了内部类 BeanPostProcessorsRegistrar,它实现了 ImportBeanDefinitionRegistrar,可以实现ImportBeanDefinitionRegistrar 来注册额外的 BeanDefinition。
导入了 ServletWebServerFactoryConfiguration.EmbeddedTomcat 等嵌入容器相关配置 EmbeddedTomcat 就是tomcat的相关配置
注册了ServletWebServerFactoryCustomizer、TomcatServletWebServerFactoryCustomizer 两个WebServerFactoryCustomizer 类型的 bean。
我们可以关注一下这两个 Customizer Bean, 这两个 Customizer 实际上就是去处理一些配置值,然后绑定到 各自的工厂类的。
WebServerFactoryCustomizer 将 serverProperties 配置值绑定给 ConfigurableServletWebServerFactory 对象实例上
TomcatServletWebServerFactoryCustomizer 主要处理 Tomcat 相关的配置值
整体结构如下图
TomcatServletWebServerFactory 是用于获取 Tomcat 作为 WebServer 的工厂类实现,其中最核心的方法就是 getWebServer,获取一个 WebServer 对象实例
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
// 创建一个 Tomcat 实例
Tomcat tomcat = new Tomcat();
// 创建一个 Tomcat 实例工作空间目录
File baseDir = (this.baseDirectory != null) ? this.baseDirectory
: createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
// 创建连接对象
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
// 1
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
// 配置 Engine,没有什么实质性的操作,可忽略
configureEngine(tomcat.getEngine());
// 一些附加链接,默认是 0 个
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
// 2
prepareContext(tomcat.getHost(), initializers);
// 返回 webServer
return getTomcatWebServer(tomcat);
}
customizeConnector : 给 Connector 设置 port、protocolHandler、uriEncoding 等。Connector 构造的逻辑主要是在NIO和APR选择中选择一个协议,然后反射创建实例并强转为 ProtocolHandler
2、prepareContext 这里并不是说准备当前 Tomcat 运行环境的上下文信息,而是准备一个 StandardContext ,也就是准备一个 web app。
对于 Tomcat 来说,每个 context 就是映射到 一个 web app 的,所以 prepareContext 做的事情就是将 web 应用映射到一个 TomcatEmbeddedContext ,然后加入到 Host 中
简单来说 prepareContext 就是在设置类加载器 WebappLoader 可以通过 setLoaderClass 和 getLoaderClass 这两个方法可以更改loaderClass 的值。所以也就意味着,我们可以自己定义一个继承 webappClassLoader 的类,来更换系统自带的默认实现。我们自定义的servlet就是这样被tomcat加载到的。
后续就是tomcat本身的过程了
上面对 SpringBoot 中内嵌 Tomcat 的过程做了分析,这个过程实际上并不复杂,就是在刷新 Spring 上下文的过程中将 Tomcat 容器启动起来,并且将当前应用绑定到一个 Context ,然后添加了 Host。
总结一下整个过程:
通过自定配置注册相关的 Bean ,包括一些 Factory 和 后置处理器等
上下文刷新阶段,执行创建 WebServer,这里需要用到前一个阶段所注册的 Bean
包括创建 ServletContext
实例化 webServer
创建 Tomcat 实例、创建 Connector 连接器
绑定 应用到 ServletContext,并添加相关的生命周期范畴内的监听器,然后将 Context 添加到 host 中
实例化 webServer 并且启动 Tomcat 服务
评论区