深⼊分析SpringBoot源码如何内嵌Tomcat容器?
作者:陌北有棵树,Java⼈,架构师社区合伙⼈!
【⼀】总述
SpringBoot的诞⽣,极⼤的简化了Spring框架的使⽤过程,提升了开发效率,可以把它理解为⼀个整合包,使⽤了SpringBoot,就可以不⽤⾃⼰去进⾏繁琐的配置,通过⼏个简单的注解,就可以构建⼀个基于REST的服务。同时,SpringBoot的快速构建部署的特性,为当下⼤热的微服务落地提供了极⼤的便利,可以说是构建微服务的理想框架。
归纳来说SpringBoot的特性有如下⼏点:
1. ⾃动配置
2. 内置tomcat、jetty、undertow 三⼤web容器
3. 将Web应⽤打成jar包启动
那么SpringBoot是怎样做到上述三个特性的呢?是我接下来的研究⽅向,本篇主要研究的是后两个特点,如何内嵌了Web容易,将应⽤打成jar包,怎么还能像Web程序⼀样运⾏。
本⽂是笔者患难与共的好兄弟Dewey Ding及笔者Debug了若⼲天的成果,谨以此篇留作纪念。
【⼆】问题引出和总体思路
按照常规的Web容器的启动⽅式,明显是⽆法和SpringBoot这种jar包的运⾏⽅式兼容的,那么他们之间是如何做到⽆缝衔接的合作呢?
最终运⾏的依然是SpringMVC框架,那么SpringBoot⼜是如何做到在内置了Tomcat的同时,⼜和SpringMVC⽆缝衔接的呢?
综上所述,整个系列需要研究的技术点如下(本⽂并未完全覆盖):
1. SpringBoot启动Tomcat
2. SpringMVC初始化DispatcherServlet
3. Tomcat的两种启动⽅式
4. Tomcat在SpringBoot中如何拿到配置
【三】SpringBoot启动过程具体分析
在SpringBoot中,⼀个Web应⽤从启动到接收请求,我粗略将它分为四步:
1. SpringBoot初始化
初始化SpringApplication:包括环境变量、资源、构造器、
开始启动:启动监听(listeners)、加载配置(environment)、创建上下⽂(applicationContext)
⾃动化配置:这个部分等到后⾯单独研究
Tomcat初始化
Tomcat接收请求
SpringMVC初始化
这⼀部分的学习真可谓⼀波三折,每次Debug SpringApplication的run⽅法,都会迷失在茫茫的源码之中,看书和博客也都是云⾥雾⾥,所以这次决定换⼀种学法,先从宏观上了解都要做什么,⾄于具体细节,等到需要的时候再去分析。⽐如今天要了解的是和Tomcat启动相关
的部分,那么就先只了解这个模块。
关于SpringBoot和Tomcat是如何合作的,在实际Debug之前,我们先抛出如下⼏个问题:
SpringBoot有main()⽅法,可以直接打成jar包运⾏;SpringMVC没有main⽅法,所以⽆法⾃⼰启动,
要依赖Tomcat,Tomcat本质是个容器,所以Tomcat中⼀定有main⽅法或者线程的start()⽅法来启动Spring程序
/WEB-INF是Web应⽤需要的,SpringMVC配置了这个,是为了Tomcat读取,从⽽启动Web容器
项⽬要部署到webapp⽬录下,才能被Tomcat运⾏
那么问题来了,SpringBoot没有做这些配置,是怎么做到内置Tomcat容器,并让Tomcat启动的呢?
【SpringBoot和Tomcat的初始化】
我们先来看Tomcat的启动时在SpringBoot启动的哪⼀步?这⾥只列举⽐较关键的⼏步:
1. SpringApplication的run⽅法
org.springframework.boot.SpringApplication#run(java.)
2. 刷新IOC容器(Bean的实例化)
org.springframework.boot.SpringApplication#refreshContext()org.springframework.boot.t.ServletWebServerApplicationContext#o 3. 创建WebServer
在org.springframework.boot.t.ServletWebServerApplicationContext#onRefresh调⽤了
createWebServer
springboot架构图
org.springframework.boot.t.ServletWebServerApplicationContext#createWebServer()
private void createWebServer() {    WebServer webServer = this.webServer;    ServletContext servletContext = getServletContext();    if (webServer
这⾥就是Tomcat的创建和启动过程了,关于下⾯这两⾏代码中,蕴含着我们尚未去发现的秘密,后⾯会继续分析(蕴含着的秘密真的坑苦了我们,竟然与它擦肩⽽过,然后绕了⼀⼤圈才回来,泪奔中~~):
this.webServer = WebServer(getSelfInitializer());······getSelfInitializer().onStartup(servletContext);
在这⾥创建了Tomcat,Connector,Host,Engine并且设置⼀些属性,关于Tomcat的具体内容,由于内容⽐较多,就不在此篇详细展开,后续会专门研究。这⾥我们发现,在Tomcat启动的时候,servletClass还没有获取到dispatcherServlet,⽽在第⼀次收到请求时,servletClass就变成了dispatch
erServlet,这⾥就对我们形成了⼀定误导,以为是第⼀次收到请求时Tomcat才加载了默认的wrapper,后来才发现出现了偏差,经历了⽆数次断点后,才回到正确的路上。
4. 这⾥不得不提到,⼀个最开始被我们忽略,后来才发现是个重要的地⽅:getSelfInitializer() 。关于SpringBoot是如何
把“/”和“dispatcherServlet”的关联给到Tomcat这件事,就蕴含在下⾯这段代码之中:
//注意 selfInitializeprivate org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {    return this::selfInitialize;}//注意 getServletCont 让我们来看两张对⽐图:
5. 通过上⾯两个Debug的断点图,我们可以看到,在执⾏了addServletContextInitializerBeans(beanFactory)和
addAdaptableBeans(beanFactory)⽅法之后,this.initializers的赋值发⽣了变化,两个Servlet,四个Filter都被赋到⾥⾯,⾄于这两个⽅法中的处理逻辑,此时已经⼼累到不想去看,暂记jira。
接下来的onStartup()⽅法,调⽤了接⼝org.springframework.boot.web.servlet.ServletContextInitializeronStartup()⽅法。
这⾥有下⾯⼏点需要注意:
SpringBoot创建Tomcat时,会先创建⼀个根上下⽂,webapplicationcontext传给tomcat
启动web容器,要先getWebserver,会创建tomcat的Webserver - 这⾥会把根上下⽂作为参数给
org.springframework.at.TomcatServletWebServerFactory#getWebServer,这⾥和tomcat的context进⾏merge
初始化servletcontext - 会把root上下⽂放进去,后⾯初始化dispatcherServlet时,会通过servletcontext拿到根上下⽂
启动tomcat:调⽤Tomcat中Host、Engine的启动⽅法
【DIspatcherServlet的初始化】
初始化DispatcherServlet,是在第⼀次发起Web请求的时候(AbstractAnnotationConfigDispatcherServletInitializer)
AbstractAnnotationConfigDispatcherServletInitializer
1. 初始化ContextLoaderListener:创建ApplicationContext(根上下⽂)
2. 初始化DispatcherServlet - 这时需要⽤ApplicationContext
3. ApplicationContext是ServletContext的⽗上下⽂(WebApplicationContext是Spring为web应⽤提供的接⼝)
4. 可以创建多个DispatcherServlet,每个可以创建⾃⼰内部的⼦上下⽂(每个Servlet都会有⼀个上下⽂)
5. DispatcherServlet建⽴上下⽂是为了持有Spring MVC的Bean对象
具体调⽤过程如下:
javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
GenericServlet实现了Servlet接⼝
这个⽅法启动SpringBoot时会调⽤⼀次,初始化dispatcherServlet时还会调⽤⼀次,不过两次的config中的serviceClass不⼀样,第⼆次是dispatcherServlet
org.springframework.web.servlet.HttpServletBean#init
org.springframework.web.servlet.FrameworkServlet#initServletBean
protected WebApplicationContext initWebApplicationContext() {        WebApplicationContext rootContext =                WebAp 获取根上下⽂(这时根上下⽂已经在SpringBoot启动时初始化好了)
然后把根上下⽂给webApplicationContext
org.springframework.web.servlet.DispatcherServlet#onRefresh - 这时就要刷新⼦上下⽂了,刷新上下⽂要刷新
HandlerMapping,HandlerAdapter这些
protected void initStrategies(ApplicationContext context) {    initMultipartResolver(context);    initLocaleResolver(context);    initThemeResolver(contex 这⾥记录⼏个重要的时间点:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。