分析SpringBoot的启动原理
作者:编程一生 发布时间:2021-10-30 16:01:23
背景
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容器进行了初始化工作。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
<version>${spring.boot.version}</version>
<exclusions>
<exclusion>
<groupId>org.eclipse.jetty.aggregate</groupId>
<artifactId>jetty-all</artifactId>
</exclusion>
</exclusions>
</dependency>
因为选择servlet容器是类似于使用基于条件的注解方式。因为当exclusion掉tomcat后,只有jetty满足条件,所以会加载JettyServletWebServerFactory。
通过getWebServer方法会new一个WebServer对象,new对象的方法会调用initialize方法,在这个方法中会对容器进行初始化并启动。
而容器启动的基本原理就是创建个线程池和网络套接字。用线程去处理套接字读写的内容。
总结
文本用带有少许说明的三个问题开场展开论述,实际是使用了麦肯锡 * 中的SCQA架构。
SCQA架构是金字塔模型里面突出的一个论述方法,即“情境(Situation)、冲突(Complication)、问题(Question)、答案(Answer)”。可以帮助我们在陈述事实时条理更为清晰、有效。
SCQA其实只是麦肯锡做了总结。这个方法李清照都在用:
昨夜雨疏风骤,浓睡不消残酒 (情境)
试问卷帘人,渠道海棠依旧(冲突)
知否,知否(问题)
应是绿肥红瘦(答案)
文章正文看似一步步回答问题,实际上在讲述怎样去看spring源码,了解spring原理的一个过程。即:带着问题去看,debug跟踪源码验证 的方法。
来源:https://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&mid=2247485489&idx=1&sn=b60689f54d5412da9e3a4c752ac548c1&utm_source=tuicool&utm_medium=referral


猜你喜欢
- 本文实例为大家分享了Java工具类DateUtils的具体代码,供大家参考,具体内容如下import java.text.ParseExce
- 有些时候我们需要监听webview的滚动事件,但WebView没有直接监听滑动的方法,看WebView的源码则会发现有一个protected
- Android 双击返回键退出程序的方法总结下面先说说LZ思路,具体如下: 1. 第一种就是根据用户点击俩次的时间间隔去判断是否退出程序;
- mybatis介绍 * 的一个作用就是我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截
- using System;using System.Collections.Generic;using System.ComponentMo
- Android音乐播放器的运行效果这篇博客还是接着上一篇Android音乐播放器制作写的,没看过的可以去看看。其中这个效果(圆形ImageV
- 一、系统介绍本系统实现扑克的分发,抢地主,电脑自动出牌等功能。二、系统展示1.扑克分发2.抢地主3.出牌4.游戏胜利三、系统实现Card.j
- 环境变量这个概念不陌生, 就是操作系统的环境变量。系统变量就是java本身维护的变量。 通过 System.getProperty 的方式获
- 先附spring各版本jar包下载链接http://repo.spring.io/release/org/springframework/s
- 编写C#程序的时候,我们都遇到过配置文件,而如今绝大多数的配置文件都是用XML写的。所以在处理的时候就需要操作XML文件。那么C#如何操作X
- 我就废话不多说了,大家还是直接看代码吧~public class GatewayContext { public static final
- 问题描述今天在给SpringBoot项目配置 * 的时候发现怎么都进不到 * 的方法里面,在搜索引擎上看了无数篇关于配置 * 的文章都没有找
- 建议缓存放到 service 层,你可以自定义自己的 BaseServiceImpl 重写注解父类方法,继承自己的实现。为了方便,这里我们将
- 回调在 * 中定义为:在计算机程序设计中,回调函数,是指通过函数参数传递到其他代码的,某一块可执行代码的引用。其目的是允许底层代码调用在高
- 1,编写一个程序,读取文件test.txt的内容并在控制台输出。如果源文件不存在,则显示相应的错误信息。 package src;impor
- 前言:由于最近有解析协议的一些业务场景,需要用到一些字节操作工具,这里封装了一些比较常用的转换方法,测试后基本没有问题,可能一些比较偏门的数
- Android的设置界面实现比较简单,有时甚至只需要使用一个简单的xml文件即可.声明简单,但是如何从PreferenceScreen或者P
- 本文实例讲述了C#入门教程之集合ArrayList用法。分享给大家供大家参考,具体如下:.NET Framework提供了用于数据存储和检索
- 一、前言在Spring中,事务有两种实现方式:编程式事务管理: 编程式事务管理使用TransactionTemplate可实现更细
- 本文实例讲述了Android编程使用pull方式解析xml格式文件的方法。分享给大家供大家参考,具体如下:上次已经说过使用Android s