详述 Spring Boot 中内嵌 Tomcat 的实现原理

对于一个 Spring Boot Web 工程来说,一个主要的依赖标志就是有spring-boot-starter-web这个starter,spring-boot-starter-web模块在 Spring Boot 中其实并没有代码存在,只是在pom.xml中携带了一些依赖,包括web、webmvc和tomcat等:

代码语言:txt复制

org.springframework.boot

spring-boot-starter

org.springframework.boot

spring-boot-starter-json

org.springframework.boot

spring-boot-starter-tomcat

org.hibernate.validator

hibernate-validator

org.springframework

spring-web

org.springframework

spring-webmvc

Spring Boot 默认的 web 服务容器是 Tomcat ,如果想使用 Jetty 等来替换 Tomcat ,可以自行参考官方文档来解决。

web、webmvc和tomcat等提供了 Web 应用的运行环境,那spring-boot-starter则是让这些运行环境工作的开关,因为spring-boot-starter中会间接引入spring-boot-autoconfigure。

WebServer 自动配置在spring-boot-autoconfigure模块中,有处理关于WebServer的自动配置类 ServletWebServerFactoryAutoConfiguration。

ServletWebServerFactoryAutoConfiguration代码片段如下:

代码语言:txt复制@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等嵌入容器相关配置(我们主要关注 Tomcat 相关的配置)。注册了ServletWebServerFactoryCustomizer、TomcatServletWebServerFactoryCustomizer两个WebServerFactoryCustomizer类型的 Bean。下面就针对这几个点,做下详细的分析。

BeanPostProcessorsRegistrarBeanPostProcessorsRegistrar这个内部类的代码如下(省略了部分代码):

代码语言:txt复制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:作用是在WebServerFactory初始化时调用上面自动配置类注入的那些WebServerFactoryCustomizer,然后调用WebServerFactoryCustomizer中的customize方法来处理WebServerFactory。ErrorPageRegistrarBeanPostProcessor:和上面的作用差不多,不过这个是处理ErrorPageRegistrar的。下面简单看下WebServerFactoryCustomizerBeanPostProcessor中的代码:

代码语言:txt复制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 Bean这两个Customizer实际上就是去处理一些配置值,然后绑定到各自的工厂类的。

WebServerFactoryCustomizer将serverProperties配置值绑定给ConfigurableServletWebServerFactory对象实例上。

代码语言:txt复制@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);

}TomcatServletWebServerFactoryCustomizer相比于上面那个,这个customizer主要处理 Tomcat 相关的配置值。

代码语言:txt复制@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());

}

}WebServerFactory用于创建WebServer的工厂的标记接口。

类体系结构class-structure上图为WebServerFactory到TomcatServletWebServerFactory的整个类结构关系。

TomcatServletWebServerFactoryTomcatServletWebServerFactory是用于获取 Tomcat 作为WebServer的工厂类实现,其中最核心的方法就是getWebServer,获取一个WebServer对象实例。

代码语言:txt复制@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 选择中选择一个协议,然后反射创建实例并强转为ProtocolHandlerprepareContext:这里并不是说准备当前 Tomcat 运行环境的上下文信息,而是准备一个StandardContext,也就是准备一个 Web App。准备 Web App Context 容器对于 Tomcat 来说,每个context就是映射到一个 Web App 的,所以prepareContext做的事情就是将 Web 应用映射到一个TomcatEmbeddedContext,然后加入到Host中

代码语言:txt复制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的类,来更换系统自带的默认实现。

初始化 TomcatWebServer在getWebServer方法的最后就是构建一个TomcatWebServer。

代码语言:txt复制// 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 服务:

代码语言:txt复制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 应用,Spring Boot 中默认启动一个 Tomcat ,并且一个 Tomcat 中只有一个 Web 应用(FATJAR 模式下,应用与 Tomcat 是 1:1 关系),所有在遍历Host下的Container时,如果Container类型是Context,就直接返回了。

代码语言:txt复制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");

}Tomcat 启动过程在TomcatWebServer的initialize方法中会执行 Tomcat 的启动。

代码语言:txt复制// Start the server to trigger initialization listeners

this.tomcat.start();org.apache.catalina.startup.Tomcat的start方法:

代码语言:txt复制public void start() throws LifecycleException {

// 初始化 server

getServer();

// 启动 server

server.start();

}初始化 Server初始化Server实际上就是构建一个StandardServer对象实例,关于 Tomcat 中的Server可以参考附件中的说明。

代码语言:txt复制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;

}小结上面对 Spring Boot 中内嵌 Tomcat 的过程做了分析,这个过程实际上并不复杂,就是在刷新 Spring 上下文的过程中将 Tomcat 容器启动起来,并且将当前应用绑定到一个Context,然后添加了Host。下图是程序的执行堆栈和执行内嵌 Tomcat 初始化和启动的时机。

nest-tomcat-start下面总结下整个过程:

通过自定配置注册相关的 Bean ,包括一些Factory和后置处理器等上下文刷新阶段,执行创建WebServer,这里需要用到前一个阶段所注册的 Bean包括创建ServletContext实例化WebServer创建 Tomcat 实例、创建Connector连接器绑定应用到ServletContext,并添加相关的生命周期范畴内的监听器,然后将Context添加到Host中实例化webServer并且启动 Tomcat 服务Spring Boot 的 Fatjar 方式没有提供共享 Tomcat 的实现逻辑,就是两个 FATJAT 启动可以只实例化一个 Tomcat 实例(包括Connector和Host),从前面的分析知道,每个 Web 应用(一个 FATJAT 对应的应用)实例上就是映射到一个Context;而对于war方式,一个Host下面是可以挂载多个Context的。

附:Tomcat 架构图及组件说明tomcat-structure组件名称

说明

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 组件间可以共享的线程池

Copyright © 2088 世界杯预选赛中国_1994年世界杯冠军是谁 - nywk120.com All Rights Reserved.
友情链接
Top