Hello,大家好,我是此林。
今天来深入底层讲一讲SpringBoot是如何启动的,也就是我们单击运行SpringBoot启动类,它底层发生了什么?
SpringBoot启动类很简单,只有一行代码。
我们点进run() 方法。
我们发现,它底层其实进行了两步操作。
第一步是new出一个SpringApplication对象,第二个是执行run() 方法。
我们先看看是如何new SpringApplication的。
继续点进SpringApplication的构造方法。
可以看到,这里初始化设置了非常多的属性。
我们主要最后几行关键代码。
通过DEBUG打断点我们发现,this.primarySources 就是当前启动类com.itheima.test.App。
也就是我们之前之前启动类里传入的参数App.class本身。
继续执行程序,这里判断了应用的类型。这里是servlet类型(传统Web应用),此外还有None类型(非Web应用)、Reactive类型。我们后期创建WebServer容器要根据这个属性来创建不同的Context,决定是否要启动Tomcat。
再看这两行代码。
1. 这里this.getSpringFactoriesInstances(ApplicationContextInitializer.class) 底层是从 META-INF/spring.factories 文件里找到实现了 ApplicationContextInitializer 接口的所有实现类。它们用于在 ApplicationContext 初始化之前进行一些自定义的初始化操作。
以下是spring.factories 文件:
2. this.getSpringFactoriesInstances(ApplicationListener.class) 也一样,底层是从 META-INF/spring.factories 文件里找到键名为
org.springframework.context.ApplicationListener
的值,也就是找到所有实现了 ApplicationListener 接口的类。它们用于监听 Spring 应用上下文中的事件。
以下是相关的spring.factories 文件:
为了更易于理解,我们举个例子,再深入看看 this.getSpringFactoriesInstances(ApplicationContextInitializer.class) 源码。
这里有三步:
1. 获取注册在META-INF/spring.factories文件中的ApplicationContextInitializer.class接口的所有实现类(一个全类名集合)
2. 对每一个实现类进行实例化(返回一个实例数组)
3. 对实例数组进行排序,表示执行顺序。排序规则如下:如果实现了 PriorityOrdered 接口,优先级最高。如果实现了Ordered接口或者基于注解@Order,数值越小优先级越高。
点进AnnotationAwareOrderComparator.sort() 方法。发现它调用了java.util.List的 sort(Comparator<? super E> c) 方法。这里传进去的INSTANCE是 AnnotationAwareOrderComparator 本身,说明 AnnotationAwareOrderComparator 实现了Comparator 接口,重写了 compare() 方法。
但是我们又发现 AnnotationAwareOrderComparator 类里似乎没有实现Comparator 接口实现自定义排序。
我们查看AnnotationAwareOrderComparator继承关系链发现,它继承了OrderComparator类;OrderComparator又实现了Comparator接口,所以AnnotationAwareOrderComparator 间接实现了Comparator 接口来自定义排序。
可以发现,主要功能是比较两个对象 o1 和 o2 的优先级顺序。
先检查 o1 和 o2 是否实现了 PriorityOrdered 接口。
如果 o1 实现了而 o2 没有实现,则 o1 的优先级更高,返回 -1。
如果 o2 实现了而 o1 没有实现,则 o2 的优先级更高,返回 1。
如果两者都实现了或都没有实现 PriorityOrdered 接口,则通过 getOrder 方法获取它们的顺序值,并比较这两个值。
我们再回到this.getSpringFactoriesInstances(ApplicationContextInitializer.class) 源码。
我们再来详细地看看这行代码的底层,它是如何获取ApplicationContextInitializer接口的所有实现类的?
点进loadFactoryNames() 方法。
这里 loadSpringFactories(classLoaderToUse) 返回的是一个 Map<String, List<String>> ,getOrDefault() 则是取到键为 factoryTypeName 的 List,取不到则返回空集合。那么factoryTypeName到底是什么?我们发现它就是 ApplicationContextInitializer 接口。
由于刚才我们看过spring.factories 文件了,它就是一个键值对文件,它的值也就是一个类名列表,所以我们推测 Map<String, List<String>> 就是整个spring.factories。
继续点进 loadSpringFactories() 方法。果然如此,详细请看注释。
我们要扫描所有的所有META_INF/spring.factories文件,因为它可能不止一个。
我们再来总结一下,以上流程就是创建初始化SpringApplication对象,并设置相关属性。关键的还是这几行代码。
至此,我们SpringApplication对象的创建就结束了。
现在我们再来详细讲一讲SpringApplication对象的run() 方法。
run() 方法点进来:
我们发现run() 方法最后返回了 ConfigurableApplicationContext ,通过继承关系链我们知道,这个接口继承了ApplicationContext接口,返回了一个Spring 容器。
流程大概如下:
1. 创建秒表记录容器创建启动的时间。
2. 加载spring.factories文件中的所有实现了 SpringApplicationRunListener接口的实现类,这些实现类是事件监听器。
SpringBoot 启动的不同阶段(如 starting, environmentPrepared, contextPrepared, contextLoaded, started, running, failed 等)会发布不同的事件,会调用这些监听器的相应方法。SpringApplicationRunListener接口只有一个实现类EventPublishingRunListener
3. run方法刚开始执行的事件广播。就是触发事件ApplicationStartingEvent,所有监听了事件ApplicationStartingEvent的ApplicationListener都会在此时执行。
4. 创建并配置当前应用将要使用的环境。
这里加载了命令行参数。
这里是准备环境,加载application.yml 之类的配置文件。
在创建环境完成够需要发布事件environmentPrepared,所以需要传入参数listeners。
5. 打印banner信息,也可以自己设置banner.txt
6. 创建applicationContext,
基于反射技术,根据应用类型new了一个ApplicationContext对象,但是并没有设置applicationContext的属性。到了这一步中context中有一个environment属性,但是它不是springApplication对象中的environment属性。
// environment.getProperty("test.string") = test
// context.getEnvironment.getProperty("test.string") = null
7. 设置应用启动策略
8. 准备应用上下文,SpringBoot自动配置在这一步。
参数:
bootstrapContext:初始化前的上下文,包含一些初始化配置和资源。
context:就是刚刚new 出来没有设置属性的 ConfigurableApplicationContext。
environment:应用环境,包含配置属性等。
listeners:应用监听器,用于监听应用生命周期事件。
applicationArguments:应用启动时传递的命令行参数。
printedBanner:启动时打印的欢迎信息(banner)。
9. 刷新容器,在这里的 invokeBeanFactoryPostProcessors() 方法
会先扫描@Controller、@Configuration等注解,再进行SpringBoot自动配置,最后再用 onRefresh() 创建启动Tomcat服务器。所以我们可以知道SpringBoot自动配置的时机是在加载用户自定义配置类之后。
关于@SpringApplication注解自动配置,这篇博客里有,这里就不赘述了。
SpringBoot自动配置原理:底层源码分析-CSDN博客
10. 刷新容器后的操作。
11. SpringBoot启动结束,关闭秒表,并且日志打印出启动时长。可以通过配置参数 spring.main.log-startup-info=false 设置是否输出
12. 通知所有的监听器,应用启动完成。调用 started 方法,传递应用上下文 context 和启动时间 timeTakenToStartup。现在已经是started阶段了,前文已经说过SpringBoot 启动的不同阶段(如 starting, environmentPrepared, contextPrepared, contextLoaded, started, running, failed 等)。
13. 调用启动器
说了这么久,我们貌似还没有说到tomcat 启动原理,既然我们想知道tomcat在SpringBoot中是怎么启动的,那么run方法中,重点关注创建应用上下文(createApplicationContext)和刷新上下文(refreshContext)。
这里做了高度封装,我们需要深入底层源码来看看。
点进 createApplicationContext()
这里的 this.applicationContextFactory 的是 DefaultApplicationContextFactory 类,所以执行的create() 方法是 DefaultApplicationContextFactory 中的。
我们点进DefaultApplicationContextFactory的create() 方法
create() 方法接收一个 WebApplicationType 参数,并尝试通过 getFromSpringFactories() 根据 WebApplicationType 创建不同的 ConfigurableApplicationContext 实例。
继续看 getFromSpringFactories() 方法。
1. SpringFactoriesLoader 先查找类路径下的 META-INF/spring.factories 文件,加载所有实现了 ApplicationContextFactory 接口的类。
2. 用for循环对所有实现类遍历,用 candidate 变量表示。由于 action 是一个 BiFunction<ApplicationContextFactory, WebApplicationType, T> 类型的函数式接口,action之前传进来的是 ApplicationContextFactory::create。所以 action.apply(candidate, webApplicationType) 相当于调用了 candidate 的 create 方法,并传入 webApplicationType,返回一个 T 类型的结果。
打个断点,我们可以清楚地看到,webApplicationType 为 "SERVLET",candidate 为 "AnnotationConfigServletWebServerApplicationContext"。
我们再去 AnnotationConfigServletWebServerApplicationContext 里面看下 create() 方法。
可以看到,它先判断WebApplicationType 是否为 SERVLET,如果是则返回 AnnotationConfigServletWebServerApplicationContext,这个为后续创建tomcat容器做了准备。
前文是createContext()方法,接下来我们再来看 refreshContext()
层层点进去,最后来到了这个方法。
继续打个断点,我们发现调用了 AnnotationConfigServletWebServerApplicationContext 的refresh() 方法,这就和createContext()创建返回的AnnotationConfigServletWebServerApplicationContext 关联起来了。
再去看下 AnnotationConfigServletWebServerApplicationContext 的 refresh() 方法。
点进 AnnotationConfigServletWebServerApplicationContext,发现它并没有refresh() 方法,于是推测在它的父类 ServletWebServerApplicationContext 中,点进 ServletWebServerApplicationContext,发现ServletWebServerApplicationContext 调用了它父类 AbstractApplicationContext 的refresh() 方法。
AbstractApplicationContext 的refresh() 方法,主要调用onRefresh方法执行自定义的刷新操作。
再看this.onRefresh(),发现是个空方法。
迂回了半天,又回到了 ServletWebServerApplicationContext
这是 ServletWebServerApplicationContext 的 onRefresh() 方法。
所以说到底,直接说 refreshContext() 最终执行的是 ServletWebServerApplicationContext 的onRefresh() 方法不就好了?
不是此林要故意迂回卖关子,这毕竟是源码阅读的一个寻找的过程。
接下来再看createWebServer() 方法,打断点,看相关变量。是不是终于有了Tomcat的迹象了?
创建Tomcat容器一定在这行代码里。
由于factory 是 TomcatServletWebServerFactory,我们直接去这个里看它的getWebServer方法。
这段代码的主要功能就是创建并配置一个Tomcat服务器实例,然后返回一个WebServer对象。相关细节注释里都写了,如果知道Tomcat源码,应该会比较熟悉。
我们看最后一行的getTomcatWebServer(tomcat)源码,这里直达底层的 TomcatWebServer类的 initialize()方法。
this.tomcat.start(); 就是启动Tomcat服务器。
内嵌tomcat的创建和启动已经看到了,那么它是怎么初始化servlet容器的呢?
就在之前我们的onRefresh() 方法里。getWebServer() 是启动Tomcat容器,getSelfInitializer()就是初始化servlet容器了。源码就不详细看了,感兴趣的小伙伴可以自己去研究研究。
本文持续更新中.......
关注我吧!老朋友此林,带你看不一样的世界!有疑惑的小伙伴可以私信我或者评论区留言,我看到后会及时回复!
往期文章:
SpringBoot自动配置原理:底层源码分析-CSDN博客
进阶版:深入浅出 Spring AOP底层原理分析(附自定义注解案例)(二)更新已完结-CSDN博客
到此这篇服务器运行springboot项目(springboot服务启动)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/rfx/51517.html