SpringBoot启动原理

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


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


1112728-20201111182550986-1155818728.png


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

 

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


1112728-20201111182640544-1890036437.png


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


JettyServletWebServerFactory是个什么东东?

 

问题2分析

 


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


1112728-20201111182716790-1853995598.png


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


1112728-20201111182748448-254360594.png


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


ServletWebServerApplicationContext的onFresh


1112728-20201111182823468-709316919.png


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


1112728-20201111182850296-1269865413.png


进入这个方法,debug到getWebServerFactory


1112728-20201111182947689-866626580.png


来看一下:


1112728-20201111183045309-1696612627.png


获取的正式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容器进行了初始化工作。


+ View Code


因为选择servlet容器是类似于使用基于条件的注解方式。因为当exclusion掉tomcat后,只有jetty满足条件,所以会加载JettyServletWebServerFactory。


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


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

 

总结


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


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


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


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


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


知否,知否(问题)


应是绿肥红瘦(答案)

相关文章
|
3月前
|
安全 Java 数据安全/隐私保护
SpringBoot实现二维码扫码登录的原理与详细步骤
SpringBoot实现二维码扫码登录的原理与详细步骤
270 1
|
1月前
|
Java 应用服务中间件 开发者
Java面试题:解释Spring Boot的优势及其自动配置原理
Java面试题:解释Spring Boot的优势及其自动配置原理
87 0
|
23天前
|
SQL Java 数据库连接
springboot~mybatis-pagehelper原理与使用
【7月更文挑战第15天】MyBatis-PageHelper是用于MyBatis的分页插件,基于MyBatis的拦截器机制实现。它通过在SQL执行前动态修改SQL语句添加LIMIT子句以支持分页。使用时需在`pom.xml`添加依赖并配置方言等参数。示例代码: PageHelper.startPage(2, 10); List&lt;User&gt; users = userMapper.getAllUsers(); PageInfo&lt;User&gt; pageInfo = new PageInfo&lt;&gt;(users); 这使得分页查询变得简单且能获取总记录数等信息。
|
1月前
|
Java 开发者 Spring
深入理解Spring Boot中的自动配置原理
深入理解Spring Boot中的自动配置原理
|
1月前
|
开发框架 Java 开发者
Spring Boot中的自动装配原理
Spring Boot中的自动装配原理
|
2月前
|
消息中间件 Java Maven
深入理解Spring Boot Starter:概念、特点、场景、原理及自定义starter
深入理解Spring Boot Starter:概念、特点、场景、原理及自定义starter
|
2月前
|
Java 应用服务中间件 Spring
解析Spring Boot自动装配的原理与机制
解析Spring Boot自动装配的原理与机制
43 4
|
2月前
|
缓存 Java 开发者
SpringBoot自动装配原理
SpringBoot自动装配原理
30 1
|
2月前
|
开发框架 Java 开发者
Spring Boot中的自动装配原理
Spring Boot中的自动装配原理
|
2月前
|
Java
SpringBoot起步依赖原理分析
SpringBoot起步依赖原理分析