原文链接: SpringBoot 中内嵌 Tomcat 的实现原理解析
对于一个 SpringBoot web 工程来说,一个主要的依赖标志就是有 spring-boot-starter-web 这个 starter ,spring-boot-starter-web 模块在 spring boot 中其实并没有代码存在,只是在 pom.xml 中携带了一些依赖,包括 web、webmvc、tomcat 等:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-json</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </dependency> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> </dependency> </dependencies> 复制代码
Spring Boot 默认的 web 服务容器是 tomcat ,如果想使用 Jetty 等来替换 Tomcat ,可以自行参考官方文档来解决。
web、webmvc、tomcat 等提供了 web 应用的运行环境,那 spring-boot-starter 则是让这些运行环境工作的开关(因为 spring-boot-starter 中会间接引入 spring-boot-autoconfigure )。
在 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 服务:
@EnableConfigurationProperties(ServerProperties.class):ServerProperties 配置中包括了常见的 server.port 等配置属性。
通过导入嵌入式容器相关的自动配置类,有 EmbeddedTomcat、EmbeddedJetty 和EmbeddedUndertow。
下面就针对这几个点,做下详细的分析。
BeanPostProcessorsRegistrar 这个内部类的代码如下(省略了部分代码):
public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware { // 省略代码 @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (this.beanFactory == null) { return; } // 注册 WebServerFactoryCustomizerBeanPostProcessor registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor", WebServerFactoryCustomizerBeanPostProcessor.class); // 注册 errorPageRegistrarBeanPostProcessor registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor", ErrorPageRegistrarBeanPostProcessor.class); } // 省略代码 } 复制代码
上面这段代码中,注册了两个 bean,一个 WebServerFactoryCustomizerBeanPostProcessor,一个 errorPageRegistrarBeanPostProcessor;这两个都实现类 BeanPostProcessor 接口,属于 bean 的后置处理器,作用是在 bean 初始化前后加一些自己的逻辑处理。
下面简单看下 WebServerFactoryCustomizerBeanPostProcessor 中的代码:
public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware { // 省略部分代码 // 在 postProcessBeforeInitialization 方法中,如果当前 bean 是 WebServerFactory,则进行 // 一些后置处理 @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof WebServerFactory) { postProcessBeforeInitialization((WebServerFactory) bean); } return bean; } // 这段代码就是拿到所有的 Customizers ,然后遍历调用这些 Customizers 的 customize 方法 private void postProcessBeforeInitialization(WebServerFactory webServerFactory) { LambdaSafe .callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory) .withLogger(WebServerFactoryCustomizerBeanPostProcessor.class) .invoke((customizer) -> customizer.customize(webServerFactory)); } // 省略部分代码 } 复制代码
这两个 Customizer 实际上就是去处理一些配置值,然后绑定到 各自的工厂类的。
将 serverProperties 配置值绑定给 ConfigurableServletWebServerFactory 对象实例上。
@Override public void customize(ConfigurableServletWebServerFactory factory) { PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); // 端口 map.from(this.serverProperties::getPort).to(factory::setPort); // address map.from(this.serverProperties::getAddress).to(factory::setAddress); // contextPath map.from(this.serverProperties.getServlet()::getContextPath) .to(factory::setContextPath); // displayName map.from(this.serverProperties.getServlet()::getApplicationDisplayName) .to(factory::setDisplayName); // session 配置 map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession); // ssl map.from(this.serverProperties::getSsl).to(factory::setSsl); // jsp map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp); // 压缩配置策略实现 map.from(this.serverProperties::getCompression).to(factory::setCompression); // http2 map.from(this.serverProperties::getHttp2).to(factory::setHttp2); // serverHeader map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader); // contextParameters map.from(this.serverProperties.getServlet()::getContextParameters) .to(factory::setInitParameters); } 复制代码
相比于上面那个,这个 customizer 主要处理 Tomcat 相关的配置值
@Override public void customize(TomcatServletWebServerFactory factory) { // 拿到 tomcat 相关的配置 ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat(); // server.tomcat.additional-tld-skip-patterns if (!ObjectUtils.isEmpty(tomcatProperties.getAdditionalTldSkipPatterns())) { factory.getTldSkipPatterns() .addAll(tomcatProperties.getAdditionalTldSkipPatterns()); } // server.redirectContextRoot if (tomcatProperties.getRedirectContextRoot() != null) { customizeRedirectContextRoot(factory, tomcatProperties.getRedirectContextRoot()); } // server.useRelativeRedirects if (tomcatProperties.getUseRelativeRedirects() != null) { customizeUseRelativeRedirects(factory, tomcatProperties.getUseRelativeRedirects()); } } 复制代码
用于创建 WebServer 的工厂的标记接口。
上图为 WebServerFactory -> TomcatServletWebServerFactory 的整个类结构关系。
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); } 复制代码
对于 Tomcat 来说,每个 context 就是映射到 一个 web app 的,所以 prepareContext 做的事情就是将 web 应用映射到一个 TomcatEmbeddedContext ,然后加入到 Host 中。
protected void prepareContext(Host host, ServletContextInitializer[] initializers) { File documentRoot = getValidDocumentRoot(); // 创建一个 TomcatEmbeddedContext 对象 TomcatEmbeddedContext context = new TomcatEmbeddedContext(); if (documentRoot != null) { context.setResources(new LoaderHidingResourceRoot(context)); } // 设置描述此容器的名称字符串。在属于特定父项的子容器集内,容器名称必须唯一。 context.setName(getContextPath()); // 设置此Web应用程序的显示名称。 context.setDisplayName(getDisplayName()); // 设置 webContextPath 默认是 / context.setPath(getContextPath()); File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase"); context.setDocBase(docBase.getAbsolutePath()); // 注册一个FixContextListener监听,这个监听用于设置context的配置状态以及是否加入登录验证的逻辑 context.addLifecycleListener(new FixContextListener()); // 设置 父 ClassLoader context.setParentClassLoader( (this.resourceLoader != null) ? this.resourceLoader.getClassLoader() : ClassUtils.getDefaultClassLoader()); // 覆盖Tomcat的默认语言环境映射以与其他服务器对齐。 resetDefaultLocaleMapping(context); // 添加区域设置编码映射(请参阅Servlet规范2.4的5.4节) addLocaleMappings(context); // 设置是否使用相对地址重定向 context.setUseRelativeRedirects(false); try { context.setCreateUploadTargets(true); } catch (NoSuchMethodError ex) { // Tomcat is < 8.5.39. Continue. } configureTldSkipPatterns(context); // 设置 WebappLoader ,并且将 父 classLoader 作为构建参数 WebappLoader loader = new WebappLoader(context.getParentClassLoader()); // 设置 WebappLoader 的 loaderClass 值 loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName()); // 会将加载类向上委托 loader.setDelegate(true); context.setLoader(loader); if (isRegisterDefaultServlet()) { addDefaultServlet(context); } // 是否注册 jspServlet if (shouldRegisterJspServlet()) { addJspServlet(context); addJasperInitializer(context); } context.addLifecycleListener(new StaticResourceConfigurer(context)); ServletContextInitializer[] initializersToUse = mergeInitializers(initializers); // 在 host 中 加入一个 context 容器 // add时给context注册了个内存泄漏跟踪的监听MemoryLeakTrackingListener,详见 addChild 方法 host.addChild(context); //对context做了些设置工作,包括TomcatStarter(实例化并set给context), // LifecycleListener,contextValue,errorpage,Mime,session超时持久化等以及一些自定义工作 configureContext(context, initializersToUse); // postProcessContext 方法是空的,留给子类重写用的 postProcessContext(context); } 复制代码
从上面可以看下,WebappLoader 可以通过 setLoaderClass 和 getLoaderClass 这两个方法可以更改loaderClass 的值。所以也就意味着,我们可以自己定义一个继承 webappClassLoader 的类,来更换系统自带的默认实现。
在 getWebServer 方法的最后就是构建一个 TomcatWebServer。
// org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) { // new 一个 TomcatWebServer return new TomcatWebServer(tomcat, getPort() >= 0); } // org.springframework.boot.web.embedded.tomcat.TomcatWebServer public TomcatWebServer(Tomcat tomcat, boolean autoStart) { Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; // 初始化 initialize(); } 复制代码
这里主要是 initialize 这个方法,这个方法中将会启动 tomcat 服务
private void initialize() throws WebServerException { logger.info("Tomcat initialized with port(s): " + getPortsDescription(false)); synchronized (this.monitor) { try { // 对全局原子变量 containerCounter+1,由于初始值是-1, // 所以 addInstanceIdToEngineName 方法内后续的获取引擎并设置名字的逻辑不会执行 addInstanceIdToEngineName(); // 获取 Context Context context = findContext(); // 给 Context 对象实例生命周期监听器 context.addLifecycleListener((event) -> { if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) { // 将上面new的connection以service(这里是StandardService[Tomcat])做key保存到 // serviceConnectors中,并将 StandardService 中的connectors 与 service 解绑(connector.setService((Service)null);), // 解绑后下面利用LifecycleBase启动容器就不会启动到Connector了 removeServiceConnectors(); } }); // 启动服务器以触发初始化监听器 this.tomcat.start(); // 这个方法检查初始化过程中的异常,如果有直接在主线程抛出, // 检查方法是TomcatStarter中的 startUpException,这个值是在 Context 启动过程中记录的 rethrowDeferredStartupExceptions(); try { // 绑定命名的上下文和classloader, ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader()); } catch (NamingException ex) { // 设置失败不需要关心 } // :与Jetty不同,Tomcat所有的线程都是守护线程,所以创建一个非守护线程 // (例:Thread[container-0,5,main])来避免服务到这就shutdown了 startDaemonAwaitThread(); } catch (Exception ex) { stopSilently(); throw new WebServerException("Unable to start embedded Tomcat", ex); } } } 复制代码
查找 Context ,实际上就是查找一个Tomcat 中的一个 web 应用,SpringBoot 中默认启动一个 Tomcat ,并且一个 Tomcat 中只有一个 Web 应用(FATJAR 模式下,应用与 Tomcat 是 1:1 关系),所有在遍历 Host 下的 Container 时,如果 Container 类型是 Context ,就直接返回了。
private Context findContext() { for (Container child : this.tomcat.getHost().findChildren()) { if (child instanceof Context) { return (Context) child; } } throw new IllegalStateException("The host does not contain a Context"); } 复制代码
在 TomcatWebServer 的 initialize 方法中会执行 tomcat 的启动。
// Start the server to trigger initialization listeners this.tomcat.start(); 复制代码
org.apache.catalina.startup.Tomcat 的 start 方法:
public void start() throws LifecycleException { // 初始化 server getServer(); // 启动 server server.start(); } 复制代码
初始化 server 实际上就是构建一个 StandardServer 对象实例,关于 Tomcat 中的 Server 可以参考附件中的说明。
public Server getServer() { // 如果已经存在的话就直接返回 if (server != null) { return server; } // 设置系统属性 catalina.useNaming System.setProperty("catalina.useNaming", "false"); // 直接 new 一个 StandardServer server = new StandardServer(); // 初始化 baseDir (catalina.base、catalina.home、 ~/tomcat.{port}) initBaseDir(); // Set configuration source ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null)); server.setPort( -1 ); Service service = new StandardService(); service.setName("Tomcat"); server.addService(service); return server; } 复制代码
上面对 SpringBoot 中内嵌 Tomcat 的过程做了分析,这个过程实际上并不复杂,就是在刷新 Spring 上下文的过程中将 Tomcat 容器启动起来,并且将当前应用绑定到一个 Context ,然后添加了 Host。下图是程序的执行堆栈和执行内嵌 Tomcat 初始化和启动的时机。
下面总结下整个过程:
SpringBoot 的 Fatjar 方式没有提供共享 Tomcat 的实现逻辑,就是两个 FATJAT 启动可以只实例化一个 Tomcat 实例(包括 Connector 和 Host ),从前面的分析知道,每个 web 应用(一个 FATJAT 对应的应用)实例上就是映射到一个 Context ;而对于 war 方式,一个 Host 下面是可以挂载多个 Context 的。
组件名称 | 说明 |
---|---|
Server | 表示整个Servlet 容器,因此 Tomcat 运行环境中只有唯一一个 Server 实例 |
Service | Service 表示一个或者多个 Connector 的集合,这些 Connector 共享同一个 Container 来处理其请求。在同一个 Tomcat 实例内可以包含任意多个 Service 实例,他们彼此独立。 |
Connector | Tomcat 连接器,用于监听和转化 Socket 请求,同时将读取的 Socket 请求交由 Container 处理,支持不同协议以及不同的 I/O 方式。 |
Container | Container 表示能够执行客户端请求并返回响应的一类对象,在 Tomcat 中存在不同级别的容器:Engine、Host、Context、Wrapper |
Engine | Engine 表示整个 Servlet 引擎。在 Tomcat 中,Engine 为最高层级的容器对象,虽然 Engine 不是直接处理请求的容器,确是获取目标容器的入口 |
Host | Host 作为一类容器,表示 Servlet 引擎(即Engine)中的虚拟机,与一个服务器的网络名有关,如域名等。客户端可以使用这个网络名连接服务器,这个名称必须要在 DNS 服务器上注册 |
Context | Context 作为一类容器,用于表示 ServletContext,在 Servlet 规范中,一个 ServletContext 即表示一个独立的 web 应用 |
Wrapper | Wrapper 作为一类容器,用于表示 Web 应用中定义的 Servlet |
Executor | 表示 Tomcat 组件间可以共享的线程池 |