导航菜单
首页 » 韦小宝 » 正文

包子-SpringBoot 中内嵌 Tomcat 的完成原理解析,你了解吗?

导言

关于一个 SpringBoot web 工程来说,一个首要的依靠标志便是有 spring-boot-starter-web 这个 starter ,spring-boot-starter-web 模块在 spring boot 中其实并没有代码存在,只是在 pom.xml 中携带了一些依靠,包括 web、webmvc、tomcat 等:



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

代码片段如下:

@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。

下面就针对这几个点,做下具体的剖析。

BeanPostProcessorsRegistrar

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:效果是在 WebServerFactory 初始化时调用上面主动装备类注入的那些 WebServerFactoryCustomizer ,然后调用 WebServerFactoryCustomizer 中的 customize 办法来 处理 WebServerFactory。
  • errorPageRegistrarBeanPostProcessor:和上面的效果差不多,不过这个是处理 ErrorPageRegistrar 的。

下面简略看下 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((WebServerFac包子-SpringBoot 中内嵌 Tomcat 的完成原理解析,你了解吗?tory) 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 方针实例上。

@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 相关的装备值

@Override
public void customize(TomcatServletWebServerFactory factory) {
// 拿到 tomcat 相关的装备
ServerProperties.Tomcat tomcatProperties = 包子-SpringBoot 中内嵌 Tomcat 的完成原理解析,你了解吗?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 的工厂包子-SpringBoot 中内嵌 Tomcat 的完成原理解析,你了解吗?的符号接口。

类体系结构

上图为 WebServerFactory -> TomcatServletWebServerFactory 的整个类结构联系。

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.additionalTomcatConnectobilionrs) {
tomcat.getService().addConnector(additionalConnector);
}
// 2
prepareContext(tomcat.getHost(), initializers);
// 回来 webServer
return getTomcatWebServer(tomcat);
}
仿制代码
  • 1、customizeConnector : 给 Connector 设置 port、protocolHandler、uriEncoding 等。Connector 结构的逻辑首要是在NIO和APR挑选中挑选一个协议,然后反射创立实例并强转为 ProtocolHandler
  • 2、prepareContext 这儿并不是说预备当时 Tomcat 运转环境的上下文信息,而是预备一个 StandardContext ,也便是预备一个 web app。

预备 Web App Context 容器

关于 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 StaticResourceConfi包子-SpringBoot 中内嵌 Tomcat 的完成原理解析,你了解吗?gurer(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。

// 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));
s包子-SpringBoot 中内嵌 Tomcat 的完成原理解析,你了解吗?ynchronized (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");
}
仿制代码

Tomcat 发动进程

在 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

初始化 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 初始化和发动的机遇。

下面总结下整个进程:

  • 经过自定装备注册相关的 Bean ,包括一些 Factory 和 后置处理器等
  • 上下文改写阶段,履行创立 WebServer,这儿需求用到前一个阶段所注册的 Bean
  • 包括创立 ServletContext
  • 实例化 webServer
  • 创立 Tomcat 实例、创立 Connector 衔接器
  • 绑定 运用到 ServletContext,并增加相关的生命周期领域内的监听器,然后将 Context 增加到 host 中
  • 实例化 webServer 而且发动 Tomcat 服务

SpringBoot 的 Fatjar 方法没有供给同享 Tomcat 的完成逻辑,便是两个 FATJAT 发动能够只实例化一个 Tomcat 实例(包括 Connector 和 Host ),早年面的剖析知道,每个 web 运用(一个 FATJAT 对应的运用)实例上便是映射到一个 Context ;而关于 war 方法,一个 Host 下面是能够挂载多个 Context 的。

附:Tomcat 组件阐明

组件称号 阐明 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 组件间能够同享的线程池

小结

欢迎重视头条号:JAVA大飞哥

点击重视谈论转发一波~~

私信小编发送“架构”(免费获取SpringBoo材料以及JAVA相关的面试架构材料哟)

最终,每一位读到这儿的Java程序猿朋友们,感谢你们本领心肠看完。期望在成为一名更优异的Java程序猿的道路上,咱们能够一同学习、一同前进!都能赢取白富美,走向架构师的人生巅峰!

二维码