SpringBoot嵌入Tomcat原理

内嵌Tomcat启动原理

  1. 首先,来到启动SpringBoot项目的地方,也就是朱配置类.
@SpringBootApplication
public class InakiApplication {public static void main(String[] args) {SpringApplication.run(InakiApplication.class, args);}}
  1. 点击@SpringBootApplication注解,进入
  2. 上图中使用**@Import注解对AutoConfigurationImportSelector** 类进行了引入,该类做了什么 事情呢?进入源码,首先调用**selectImport()**方法,在该方法中调用了 **getAutoConfigurationEntry()方法,在之中又调用了getCandidateConfigurations()**方法, getCandidateConfigurations()方法就去META-INF/spring.factories配置文件中加载相关配置类。

META-INF/spring.factories: SpringBoot的SPI机制。SpringBoot在启动的时候会扫描所有该名称的文件,拿到所有需要自动装配的类,对其进行扫描。

META-INF/spring.factories在spring-boot-autoconfiguration:XXX.release包中

继续查看改配置类的org.springframework.boot.autoconfigure.EnableAutoConfiguration的值
tomcat服务器的自动配置类就是这个ServletWebServerFactoryAutoConfigutation,点击进入该类。

  1. 首先看下该类的注释

Auto-configuration for servlet web servers.

翻译:servlet web 服务器的自动配置

  1. 继续看下该类的注解
    进入该类,里面也通过@Import注解将EmbeddedTomcat、EmbeddedJetty、 EmbeddedUndertow等嵌入式容器类加载进来了,springboot默认是启动嵌入式tomcat容器, 如果要改变启动jetty或者undertow容器

  2. 看下EnableConfigurationProperties这个注解中的ServerProperties

// 去配置文件中找以server开头的配置信息
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {/*** 设置服务器 端口号*/private Integer port;/*** 设置web服务器 地址*/private InetAddress address;

这也就验证了我们为什么要在application.properties/application.yml文件中配置server.port、server.address就会生效的原因。

  1. 我们点击进入EmbeddedTomcat这个类

主要看tomcatServletWebServerFactory这个方法

@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {// 该类生效必须满足@ConditionalOnClass和@ConditionalOnMissingBean注解中所声明的条件// @Con@ConditionalOnMissingBeanditionalOnClass,由于我们导入了tomcat的依赖包,所有上下文中肯定存在这三个类// @ConditionalOnMissingBean, 上下文中不存在ServletWebServerFactory类@Configuration(proxyBeanMethods = false)@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)public static class EmbeddedTomcat {@Beanpublic TomcatServletWebServerFactory tomcatServletWebServerFactory(ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,ObjectProvider<TomcatContextCustomizer> contextCustomizers,ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {// 1. 创建tomcatwebServer 工厂TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();// 以下几个方法都是设置tomcatwebServeFactory的属性值factory.getTomcatConnectorCustomizers().addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));factory.getTomcatContextCustomizers().addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));factory.getTomcatProtocolHandlerCustomizers().addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));return factory;}}/*** 由于没导入jetty的包,第二个注解中的条件得不到满足,所以该类不会被加载更不会被自动配置*/@Configuration(proxyBeanMethods = false)@ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)public static class EmbeddedJetty {@Beanpublic JettyServletWebServerFactory JettyServletWebServerFactory(ObjectProvider<JettyServerCustomizer> serverCustomizers) {JettyServletWebServerFactory factory = new JettyServletWebServerFactory();factory.getServerCustomizers().addAll(serverCustomizers.orderedStream().collect(Collectors.toList()));return factory;}}/*** 同理 EmbeddedJetty.class*/@Configuration(proxyBeanMethods = false)@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)public static class EmbeddedUndertow {@Beanpublic UndertowServletWebServerFactory undertowServletWebServerFactory(ObjectProvider<UndertowDeploymentInfoCustomizer> deploymentInfoCustomizers,ObjectProvider<UndertowBuilderCustomizer> builderCustomizers) {UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();factory.getDeploymentInfoCustomizers().addAll(deploymentInfoCustomizers.orderedStream().collect(Collectors.toList()));factory.getBuilderCustomizers().addAll(builderCustomizers.orderedStream().collect(Collectors.toList()));return factory;}}}

文中代码已经注释。

  1. 该类中,只能满足EmbbeddTomcat类满足条件。下面进入TomcatServletWebServerFactory

下面有一个关键的方法 getWebServer,他就是类创建Tomcat的并完成启动。

public WebServer getWebServer(ServletContextInitializer... initializers) {if (this.disableMBeanRegistry) {Registry.disableRegistry();}// 创建tomcatTomcat tomcat = new Tomcat();// 设置baseDirFile baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");tomcat.setBaseDir(baseDir.getAbsolutePath());// 获取Connector,用于接收处理请求Connector connector = new Connector(this.protocol);connector.setThrowOnFailure(true);// 将Connector添加进webServer服务器tomcat.getService().addConnector(connector);customizeConnector(connector);tomcat.setConnector(connector);// 关闭自动部署tomcat.getHost().setAutoDeploy(false);// 设置 EngineconfigureEngine(tomcat.getEngine());for (Connector additionalConnector : this.additionalTomcatConnectors) {tomcat.getService().addConnector(additionalConnector);}prepareContext(tomcat.getHost(), initializers);return getTomcatWebServer(tomcat);}

最后return getTomcatWebServer(tomcat); 继续进入getTomcatWebServer()

protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {return new TomcatWebServer(tomcat, getPort() >= 0);}public TomcatWebServer(Tomcat tomcat, boolean autoStart) {Assert.notNull(tomcat, "Tomcat Server must not be null");this.tomcat = tomcat;this.autoStart = autoStart;// 启动方法在这里面initialize();
}


到这里,就是SpringBoot的启动自动配置Tomcat容器并启动的原理。

SpringBoot启动过程中,在哪里配置Tomcat

上面说到,tomcat启动的核心方法是在TomcatServletWebServerFactory#getWebServer()方法中,

下面就来看一下,getWebServer是在哪里被调用的。

getWebServer的调用流程

点击run方法,一直进入到SpringApplication#run(String… args)方法中。

public ConfigurableApplicationContext run(String... args) {//StopWatch主要是用来统计每项任务执行时长,例如Spring Boot启动占用总时长。StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = newArrayList<>();configureHeadlessProperty();//第一步:获取并启动监听器 通过加载META-INF/spring.factories 完成了SpringApplicationRunListener实例化工作SpringApplicationRunListeners listeners = getRunListeners(args);//实际上是调用了EventPublishingRunListener类的starting()方法listeners.starting();try {ApplicationArguments applicationArguments = newDefaultApplicationArguments(args);//第二步:构造容器环境,简而言之就是加载系统变量,环境变量,配置文件ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);//设置需要忽略的beanconfigureIgnoreBeanInfo(environment);//打印bannerBanner printedBanner = printBanner(environment);//第三步:创建容器context = createApplicationContext();//第四步:实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误exceptionReporters =getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class },context);//第五步:准备容器 这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。prepareContext(context, environment, listeners,applicationArguments, printedBanner);//第六步:刷新容器 springBoot相关的处理工作已经结束,接下的工作就交给了spring。 内部会调用spring的refresh方法,// refresh方法在spring整个源码体系中举足轻重,是实现 ioc 和 aop的关键。refreshContext(context);//第七步:刷新容器后的扩展接口 设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。afterRefresh(context, applicationArguments);stopWatch.stop();if (this.logStartupInfo) {newStartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(),stopWatch);}//发布应用已经启动的事件listeners.started(context);/*这个方法大概做了以下几件事1. 获取并启动监听器 通过加载META-INF/spring.factories 完成了SpringApplicationRunListener实例化工作2. 构造容器环境,简而言之就是加载系统变量,环境变量,配置文件3. 创建容器4. 实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误5. 准备容器6. 刷新容器7. 刷新容器后的扩展接口那么内置tomcat启动源码,就是隐藏在上诉第六步:refreshContext方法里面,该方法最终会调用到AbstractApplicationContext类的refresh()方法进入refreshContext()方法,如图:* 遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法。* 我们可以实现自己的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进行扩展。*/callRunners(context, applicationArguments);}catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, listeners);throw new IllegalStateException(ex);}try {//应用已经启动完成的监听事件listeners.running(context);}catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, null);throw new IllegalStateException(ex);}return context;
}

这个方法主要流程是下面这7个方法,上面代码中已经标记了序号。

  1. 获取并启动监听器 通过加载META-INF/spring.factories 完成了 SpringApplicationRunListener实例化工作
  2. 构造容器环境,简而言之就是加载系统变量,环境变量,配置文件
  3. 创建容器
  4. 实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
  5. 准入容器
  6. 刷新容器refresh
  7. 刷新容器的扩展接口

内置tomcat的过程就在第六步中的refresh()方法中

点击进入,来到AbstractApplicationContext#refresh().代码进行了精简

public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// 准备上下文环境prepareRefresh();// 获取beanFactoryConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// 对beanFactory进行一些属性设置以及填充prepareBeanFactory(beanFactory);try {// 加载装配beanFactory的后置处理器postProcessBeanFactory(beanFactory);// 执行beanFactory的后置处理器// 自动配置类的获取就在这里,此时所有的bean保存在BeanDefinition中。invokeBeanFactoryPostProcessors(beanFactory);// 注册beanPostProsessor bean级别的后置处理器registerBeanPostProcessors(beanFactory);// 处理国际化相关的内容initMessageSource();// 初始化ApplicationEventMulticasterinitApplicationEventMulticaster();// 加载web服务器,tomcat的初始化就在这里onRefresh();// 注册监听器registerListeners();// 加载所有延迟初始化的beanfinishBeanFactoryInitialization(beanFactory);// 完成web服务器的启动finishRefresh();}}}

tomcat的装配流程就在onRefresh()方法中,点击进入。

进入之后发现这是一个模板方法,需要子类来重写实现。

点击进入ServletWebServerApplicationContext

protected void onRefresh() {// 调用父类方法super.onRefresh();try {// 核心,创建TomcatWebServercreateWebServer();}catch (Throwable ex) {throw new ApplicationContextException("Unable to start web server", ex);}}

createWebServer()就是启动web服务,但是还没有真正启动Tomcat

断点进入:

webServer和servletContext都是空,进入179行代码从工厂中获取ServletWebServerApplicationContext#getWebServerFactory()最后一行

它要获取的是ServletWebServerFacyory类型的工厂。我看来分析一下ServletWebServerFacyory类。他是一个接口,就是有getWebServer这一个方法。这个方法是不是很眼熟。没错,就是TomcatServletWebServerFactory中getWebServer方法,也就是tomcat的核心配置流程。

这个工厂就有我们刚刚上面再讲Tomcat自动配置时提到的XXXWebServerFactory,可以看到这里有Tomcat和Jetty。
放行断点:

此时拿到的就是TomcatServletWebServerFactory并调用他的getWebServer方法。

走到这里,tomcat的配置流程就走完了。

小结

springboot的内部通过 new Tomcat() 的方式启动了一个内置Tomcat。但是这里还有一个问题, 这里只是启动了tomcat,但是我们的springmvc是如何加载的呢?下一章我们讲接收, springboot是如何自动装配springmvc的

流程图:

相关文章:
SpringBoot嵌入SpringMVC原理分析

SpringBoot嵌入Tomcat原理分析相关推荐

  1. SpringBoot自动装配原理分析

    在springBoot中,我们不需要做任何的配置,可以直接编写业务层面的代码,原因就是springBoot帮我们配置好了所有的环境. 下面以一个简单的springboot项目进行分析 项目代码 spr ...

  2. SpringBoot启动Tomcat原理与嵌入式Tomcat实践

    导读 作为一个开发,使用Spring Boot 时,和传统的Tomcat 部署相比,我们只需要关注业务的开发,项目的启动和部署变的十分简单, 那么它背后是怎么实现的, 隐藏着什么? 本文先从一个嵌入式 ...

  3. springboot嵌入tomcat文件上传,虚拟路径配置

    场景: 如果是独立的tomcat,我们直接将文件上传到tomcat的web路径下即可进行访问 对于springboot内嵌的tomcat,当我们启动jar包时,不可能将文件上传到tomcat路径下,这 ...

  4. 总结:SpringBoot内嵌Tomcat原理

    一.介绍 一般我们启动web服务都需要单独的去安装tomcat,而Springboot自身却直接整合了Tomcat,什么原理呢? 二.原理 SpringBoot应用只需要引入spring-boot-s ...

  5. 浅谈:Spring Boot原理分析,切换内置web服务器,SpringBoot监听项目(使用springboot-admin),将springboot的项目打成war包

    浅谈:Spring Boot原理分析(更多细节解释在代码注释中) 通过@EnableAutoConfiguration注解加载Springboot内置的自动初始化类(加载什么类是配置在spring.f ...

  6. Tomcat线程池监控及线程池原理分析

      目录         一.背景         二.tomcat线程池监控         三.tomcat线程池原理         四.总结 一.背景 我们都知道稳定性.高可用对于一个系统来讲 ...

  7. boot入门思想 spring_(二 )SpringBoot起飞之路-入门原理分析

    随便给最近想逐步整理的文章起了个皮皮虾名:SpringBoot起飞之路 这是第二篇,关于前一篇入门涉及到的一些基本原理讲解,有兴趣的朋友可以去了解一下前一篇 SpringBoot起飞之路-HelloW ...

  8. 嵌入式tomcat的不使用web.xml原理分析

    前言 前面的章节讲了嵌入式tomcat使用web.xml启动,这章说说不使用web.xml启动tomcat.有了这个就明白Spring Boot是如何在没有web.xml的的情况下实现web能力的,以 ...

  9. tomcat原理简要分析,java

    tomcat原理 tomcat位置 tomcat实际上是部署在服务器上的: tomcat作用 tomcat服务器是一个Servlet和JSP容器,它响应HTML页面的访问请求. 实际上Tomcat是A ...

最新文章

  1. bootstrap table传回的数据后端怎么获取_Flasksqlalchemy让你的数据库和网页执手偕老吧...
  2. 专家:人工智能开始对现实世界产生重大影响
  3. VS2005 和 SQL Server 2005 安装顺序不同会发生什么?
  4. 通用客户端表单验证函数修正版
  5. 外挂学习之路(5)--- 写测试call的注意事项
  6. micropython安装第三方库_【python操作】python安装第三方库的方法总结
  7. [C++11]字符串原始字面量
  8. 前端学习(3318):异步处理thunk
  9. 考研还是直接工作?嵌入式or前端
  10. 《操作系统》学习辅导
  11. [转]C语言如何获得精确到毫秒的时间
  12. 如何获取html输入框的值,jQuery如何获取各种input输入框的值
  13. PHP全局变量与SESSION 漏洞(global 与 session)
  14. VUE项目开发的完整流程
  15. python是最适合初学者的语言
  16. UNI/TUBE2新配对上线,Eswap迎来新机遇
  17. C++【类与对象】——运算符重载
  18. eclipse修改java jdk(以1.8为例)
  19. 与人斗,其乐无穷!- 职场做人天天练 ! [推荐]
  20. 计算集群MOSIX-3.1.1.1.for_kernel-3.2.23 配置

热门文章

  1. 2.(C语言)百钱白鸡问题:100元钱买100只鸡,公鸡一只5元钱,母鸡一只3元钱,小鸡一元钱三只,求100元钱能买公鸡、母鸡、小鸡各多少只?
  2. PolarDB-X 2.1 新版本发布 让“MySQL 原生分布式”触手可及
  3. Newman学习(一)
  4. centos下重启apache和mysql的方法
  5. android goToSleep无法进入deep sleep分析
  6. centos gedit 字体大小_干货 | SolidWorks工程图尺寸标注字体大小更改方法
  7. Adobe国际认证-摄影的供求:成为专业摄影师需要什么
  8. SFTP命令用法(上传和下载 )
  9. html tag noscript
  10. koala框架+Mybatis+Oracle