分析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虚拟机规范定义要等所有用户线程都运行完才会退出。

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

分析SpringBoot的启动原理

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

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

分析SpringBoot的启动原理

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

问题2分析

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

分析SpringBoot的启动原理

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

分析SpringBoot的启动原理

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

ServletWebServerApplicationContext的onFresh

分析SpringBoot的启动原理

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

分析SpringBoot的启动原理

进入这个方法,debug到getWebServerFactory

分析SpringBoot的启动原理

来看一下:

分析SpringBoot的启动原理

获取的正式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

标签:SpringBoot,启动,原理
0
投稿

猜你喜欢

  • IntellJ IDEA神器使用技巧(小结)

    2023-08-08 22:01:44
  • 使用Java实现qq邮箱发送邮件

    2023-10-14 18:24:36
  • java使用RandomAccessFile类基于指针读写文件实例代码

    2021-07-29 17:45:45
  • Java受检异常的一些思考

    2021-06-08 08:22:16
  • JAVA 实现磁盘文件加解密操作的示例代码

    2023-11-15 00:13:06
  • java商城项目实战之购物车功能实现

    2023-11-02 21:17:43
  • SpringBoot 如何从配置文件读取值到对象中

    2023-10-13 15:43:15
  • java冷知识:javac AbstractProcessor详解

    2022-08-01 19:32:09
  • Java Volatile关键字你真的了解吗

    2023-08-09 20:28:23
  • Spring MVC深入学习之启动初始化过程

    2023-07-16 07:04:21
  • 简单探索 Java 中的惰性计算

    2023-11-16 20:25:28
  • java字节码框架ASM的深入学习

    2023-11-29 05:51:19
  • Android 文件数据存储实例详解

    2023-07-28 17:08:03
  • Java通过PropertyDescriptor反射调用set和get方法

    2023-10-11 19:34:17
  • Android自定义View之组合控件实现类似电商app顶部栏

    2023-07-26 22:14:10
  • 详解@ConfigurationProperties实现原理与实战

    2023-11-24 05:19:26
  • 详解SpringCloud mysql实现配置中心

    2023-11-02 05:12:57
  • Spring AOP源码深入分析

    2023-08-15 13:01:16
  • C# WPF 自定义按钮的方法

    2021-08-30 23:42:11
  • Java使用Tess4J实现图像识别方式

    2022-10-07 19:24:11
  • asp之家 软件编程 m.aspxhome.com