第十篇

SpringBoot启动原理
背景

1> 大家都知道SpringBoot是通过main函数启动的,这里面跟踪代码到处都没有找到while(true),为什么启动后可以一直跑?

2> SpringBoot默认使用tomcat作为web容器。大家也可以通过在pom文件中exclusion掉tomcat,denpendency jetty 的方法来使用jetty。那SpringBoot是怎么做到在不同web容器之间切换的呢?

3> 传统的web容器比如jetty本质上是直接通过java start.jar 来启动,之后来加载spring上下文的,SpringBoot通过main函数是怎么来启动web容器的呢?

本文就这三个问题展开论述。

问题1分析

问题1很简单,启动后一直跑是因为启动了线程池。原理就是有非deamon的线程在跑。Java虚拟机规范定义要等所有用户线程都运行完才会退出。

所以这个原理就和下面启动线程池一样

程序员修炼之道教我们:不要假定,要证明。虽然jetty使用线程池是常识,我们也来跟踪下源码,看看线程池是在哪里初始化的:

org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory类里,创建Server的使用使用线程池作为初始化参数。然后创建了socket连接来监听端口。(对于socket连接有之前没接触过的,可以自己查一下。建议动手实践。《Java异常处理总结》这篇文章里有不错的简单小例子可以实操下。)

到这里,大家应该都明白了为什么启动后一直不停。但是又有疑问了:JettyServletWebServerFactory是个什么东东?

问题2分析

关于问题2,我们写个最简单的类来debug一下:

进入SpringAppication.run的源码可以看到,里面创建了一个context,默认是AnnotationConfigServletWebServerApplicationContext。一初始化,在Bean定义里就加载了spring开天辟地的5个Bean。

继续向下执行走到AbstractApplicationContext的refresh方法,执行到onRefresh时,你进入方法里发现实际上执行的是

ServletWebServerApplicationContext的onFresh

这里面实际只做了一件事:创建web服务。

进入这个方法,debug到getWebServerFactory

来看一下:

获取的正式JettyServletWebServerFactory。为啥不是TomcatServlet呢?ServletWebServerFactoryAutoConfiguration的源码很好的说明了这个问题。源码的大意是当tomcat依赖存在就用tomcat,不然就按顺序找jetty存不存在,不存在再找Undertow存不存在。找到了就返回这个bean作为Servlet的工厂类。

@Configuration
@AutoConfigureOrder(-2147483648)
@ConditionalOnClass({ServletRequest.class})
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@EnableConfigurationProperties({ServerProperties.class})
@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration {
public ServletWebServerFactoryAutoConfiguration() {
}

@Bean
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
    return new ServletWebServerFactoryCustomizer(serverProperties);
}

@Bean
@ConditionalOnClass(
    name = {"org.apache.catalina.startup.Tomcat"}
)
public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
    return new TomcatServletWebServerFactoryCustomizer(serverProperties);
}

public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
    private ConfigurableListableBeanFactory beanFactory;
    public BeanPostProcessorsRegistrar() {
    }

    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        if (beanFactory instanceof ConfigurableListableBeanFactory) {
            this.beanFactory = (ConfigurableListableBeanFactory)beanFactory;
        }

    }

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        if (this.beanFactory != null) {
            this.registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor", WebServerFactoryCustomizerBeanPostProcessor.class);
            this.registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor", ErrorPageRegistrarBeanPostProcessor.class);
        }
    }

    private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class<?> beanClass) {
        if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
            RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
            beanDefinition.setSynthetic(true);
            registry.registerBeanDefinition(name, beanDefinition);
        }

    }
}

}
至此第二个问题也真相大白。

问题3分析

第三个问题是传统的web容器比如jetty本质上是直接通过java start.jar 来启动,之后来加载spring上下文的,SpringBoot通过main函数是怎么来启动web容器。

这个问题在前面问题分析过程中也给了很多线索。我们来回顾下:SpringApplication.run里会创建Spring的应用上下文,默认是AnnotationConfigServletWebServerApplicationContext。首先会加载Spring开天辟地的5个Bean。然后它初始化各种Bean工厂。

SpringBoot在ServletWebServerApplicationContext中重载了onRefresh方法,除了以前Spring默认的onRefresh方法外还增加了createWebServer方法,在这个方法中对Web容器进行了初始化工作。

org.springframework.boot spring-boot-starter-web ${spring.boot.version} org.springframework.boot spring-boot-starter-logging org.springframework.boot spring-boot-starter-tomcat com.fasterxml.jackson.core jackson-databind org.springframework.boot spring-boot-starter-jetty ${spring.boot.version} org.eclipse.jetty.aggregate jetty-all 因为选择servlet容器是类似于使用基于条件的注解方式。因为当exclusion掉tomcat后,只有jetty满足条件,所以会加载JettyServletWebServerFactory。

通过getWebServer方法会new一个WebServer对象,new对象的方法会调用initialize方法,在这个方法中会对容器进行初始化并启动。

而容器启动的基本原理就是创建个线程池和网络套接字。用线程去处理套接字读写的内容。

总结

文本用带有少许说明的三个问题开场展开论述,实际是使用了麦肯锡大法中的SCQA架构。

SCQA架构是金字塔模型里面突出的一个论述方法,即“情境(Situation)、冲突(Complication)、问题(Question)、答案(Answer)”。可以帮助我们在陈述事实时条理更为清晰、有效。

SCQA其实只是麦肯锡做了总结。这个方法李清照都在用:

昨夜雨疏风骤,浓睡不消残酒 (情境)

试问卷帘人,却道海棠依旧(冲突)

知否,知否(问题)

应是绿肥红瘦(答案)

文章正文看似一步步回答问题,实际上在讲述怎样去看spring源码,了解spring原理的一个过程。即:带着问题去看,debug跟踪源码验证 的方法。

标签: 整体架构
好文要顶 关注我 收藏该文
编程一生
关注 - 17
粉丝 - 1527
+加关注
10
« 上一篇: hystrix线程池隔离的原理与验证
posted @ 2020-11-11 18:33 编程一生 阅读(166) 评论(1) 编辑 收藏

评论
#1楼 2020-11-11 22:34 | 歪头儿在帝都
最后几句诗句总结很到位

支持(0) 反对(0)
刷新评论刷新页面返回顶部
登录后才能发表评论,立即 登录 或 注册, 访问 网站首页
博客园派送云上免费午餐,AWS注册立享12个月免费套餐
【推荐】News: 大型组态、工控、仿真、CADGIS 50万行VC++源码免费下载
【推荐】博客园 & 陌上花开HIMMR 给单身的程序员小哥哥助力脱单啦~
【推荐】博客园x示说网联合策划,AI实战系列公开课第二期
【推荐】了不起的开发者,挡不住的华为,园子里的品牌专区
【推荐】未知数的距离,毫秒间的传递,声网与你实时互动
【推荐】 阿里云双十一返场继续,云服务器0.73折起
【推荐】年薪100w+的技术人,都做对了什么?

相关博文:
· SpringBoot
· SpringBoot
· SpringBoot
· Springboot
· 【SpringBoot】SpringBoot国际化(七)
» 更多推荐…

最新 IT 新闻:
· 奔驰柏林发动机工厂主管跳槽特斯拉,管理新工厂?
· 谷歌相册明年取消无限容量:不花钱最多15GB
· SpaceX完成火箭静态点火测试,周末将载人发射
· 国家邮政局:双11当天6.75亿件快件 创历史新高
· 美股科技股反弹 双11拼多多涨8%京东涨3%阿里微跌
» 更多新闻…

昵称: 编程一生
园龄: 3年8个月
粉丝: 1527
关注: 17
+加关注
我的标签
整体架构(46)
入门基础(31)
人生哲学(24)
团队建设(12)
高可用(11)
漫画系列(10)
项目随记(9)
求职面试(7)
搜索引擎(5)
epiphany(3)
更多
积分与排名
积分 - 322567
排名 - 1424
随笔档案 (187)
2020年11月(1)
2020年10月(1)
2020年9月(4)
2020年6月(4)
2020年5月(3)
2020年4月(1)
2020年3月(1)
2019年10月(7)
2019年9月(3)
2019年8月(3)
2019年7月(1)
2019年6月(10)
2019年5月(8)
2019年4月(13)
2019年3月(13)
2019年2月(8)
2019年1月(3)
2018年12月(1)
2018年11月(1)
2018年9月(1)
2018年8月(7)
2018年7月(5)
2018年6月(5)
2018年5月(9)
2018年4月(3)
2018年3月(2)
2018年2月(4)
2018年1月(3)
2017年12月(1)
2017年11月(4)
2017年10月(3)
2017年9月(6)
2017年8月(5)
2017年7月(2)
2017年6月(9)
2017年5月(11)
2017年4月(8)
2017年3月(9)
2017年2月(4)
最新评论

  1. Re:SpringBoot启动原理
    最后几句诗句总结很到位

–歪头儿在帝都
2. Re:专治不会看源码的毛病–spring源码解析AOP篇
@脸滚键盘丶 哇,太感谢啦。…
–编程一生
3. Re:专治不会看源码的毛病–spring源码解析AOP篇
文章被盗了,还标的原创,还打广告,太气人了
链接:

–脸滚键盘丶
4. Re:hystrix线程池隔离的原理与验证
静姐,你从美团离职去哪了?

–无信不立
5. Re:hystrix线程池隔离的原理与验证
代码需要排版了

–歪头儿在帝都
阅读排行榜

  1. 漫画:全面理解java.lang.IllegalArgumentException及其可用性设计(23247)
  2. Redis和消息队列使用实战(17325)
  3. 一个高性能、轻量级的分布式内存队列系统–beanstalk(16235)
  4. 和真正的程序员在一起是怎样的体验(15146)
  5. 专治不会看源码的毛病–spring源码解析AOP篇(14362)
    评论排行榜
  6. 和真正的程序员在一起是怎样的体验(132)
  7. 程序媛是怎样找老公的(69)
  8. 给程序员老公20年后的一封信(60)
  9. 架构师之路–从业务角度谈缓存的选型(50)
  10. 架构师之路–谈架构师的基本素养和[干货]日志处理(49)
    推荐排行榜
  11. 和真正的程序员在一起是怎样的体验(88)
  12. 给程序员老公20年后的一封信(73)
  13. 程序媛是怎样找老公的(56)
  14. 架构师之路–怎样聊技术天,限流技术和各类编程语言(36)
  15. 程序员是一个什么能力都可以发挥作用的平台(35)
    Copyright © 2020 编程一生
    Powered by .NET 5.0.0 on Kubernetes
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值