SpringCloud启动源码分析

前言

​   在前面的博客中我们已经详细分析过Spring的启动源码,包括beanDefinition的加载、bean的初始化以及各种后处理器的回调,这个过程还是比较复杂的。

​   作为Spring全家桶中的一员,SpringCloud也是对Spring做的封装,底层逻辑其实一致,我们先来看看SpringCloud的入口方法:

​    我们直接去看看SpringApplicationrun 方法,根据调用形式可以看到这里调用的是SpringApplication的静态run方法:

​   这部分的代码逻辑是:先创建一个SpringApplication对象,然后调用这个对象的的run 方法。所以核心逻辑有两个部分:SpringApplication的构造方法和它的非静态run方法。

1. SpringApplication的构造方法

​   我们从源码可以看到,SpringApplication类没有继承任何类,也没有实现任何接口(我们在讲解Spring时,ClassPathXmlApplicationContext类图就非常复杂)。

​   我们接着来看看SpringApplication的构造方法,该构造方法的核心是调用getSpringFactoriesInstances方法,完成三个类型bean的实例化,注意这一步我们并未创建beanFactory:

​   这里完成了三个类型bean的实例化:BootstrapRegistryInitializerApplicationContextInitializerApplicationListener

​    我们接着看看getSpringFactoriesInstances方法,该方法核心逻辑有两个:

​   1.通过SpringFactoriesLoaderloadFactoryNames获得指定类型的类的全路径名;

​   2.通过createSpringFactoriesInstances创建实例;

​   getSpringFactoriesInstances 首先会通过getClassLoader方法去获得类加载器:

​   类加载的一个作用是根据字节码文件创建Class对象,并保存在堆内存中。因此,创建一个对象是绕不开类加载器的,只不过大部分情况下是jvm帮我们完成了这些工作。

​   在获得类加载器后,会调用SpringFactoriesLoader的静态方法loadFactoryNames ,该方法的作用是找到我们指定类的全路径名,有了类的全路径名才能加载Class对象并创建该类的对象,我们接着来看看loadFactoryNames方法:

​   loadFactoryNames 的底层逻辑很好理解:通过静态方法loadSpringFactories找到所有第三方依赖包中的文件spring.factories的类的全路径名,然后进一步筛选出指定类型的全路径名并返回。

​   loadSpringFactories方法的底层逻辑是这样的:

ABAP 复制代码
1.根据类加载器查缓存(SpringFactoriesLoader的cache属性),结果不为空就直接返回;
2.结果为空,就去找有"META-INF/spring.factories"文件的第三方依赖包的路径;
3.遍历2中得到的路径,根据路径加载spring.factories文件中的全路径名;
ABAP 复制代码
4.遍历全路径名和实现类将其保存在result;
5.对result的value去重,并将value更改为不可修改的列表;
6.将入参classLoader作为key,result作为value,存入SpringFactoriesLoader的cache属性中。


​   当loadFactoryNames 方法执行完成后,我们再回到getSpringFactoriesInstances方法:

​   接下来就是通过createSpringFactoriesInstances 方法创建实例对象了,我们可以进到createSpringFactoriesInstances方法看看,其核心思想是通过反射创建对象:

​   以上就是getSpringFactoriesInstances方法的主要逻辑,总的来说就干了一件事,实例化指定类型的对象。让我们再回到SpringApplication的构造方法:

2.SpringApplication的非静态run方法

​   run 方法会对SpringApplication继续做一些配置。

​   其中重要的是会创建一个上下文容器(类型为AnnotationConfigServletWebServerApplicationContext),就是我们Spring中讲过的上下文容器(类型为ClassPathXmlApplicationContext),二者类型有区别,但是也负责实例化bean:

​   之前讲解Spring容器的时候详细讲过refresh 方法干了什么,虽然上下文容器的类型不同,但是处理的逻辑是大致相同,我们这里就简单介绍一下,不过多深入,首先我们来看看refreshContext方法:

​   最后还是调用到了AbstractApplicationContextrefresh 方法,是不是很熟悉,没错就是ClassPathXmlApplicationContext调用的refresh 方法,但是Spring Cloud在Spring基础上做了封装修改,refresh方法也有小改动,但是大体并没有变:

​   到了这一步,相信看了Spring源码文章的朋友们就非常熟悉了,这就是Spring启动的核心代码。

​   需要注意的有两点:

​   1.obtainFreshBeanFactory 方法:在Spring中该方法会调用AbstractRefreshableApplicationContextrefreshBeanFactory 方法,会完成beanDefinition加载。而在SpringCloud中,该方法会调用GenericApplicationContext中的refreshBeanFactory方法,该方法只设置了beanFactory的序列化id,并不会加载beanDefinition。

​   这里还有一个小细节需要注意一下,我们可以看到AbstractRefreshableApplicationContextrefreshBeanFactory方法会去创建一个beanFactory,那么SpringCloud是在哪创建beanFacotry的呢?

​   2.invokeBeanFactoryPostProcessors 方法:在SpringCloud中该方法负责加载beanDefinition。其实SpringCloud对Spring做了进一步的封装体现在,SpringCloud自己定义了许多BeanFactoryPostProcessor类型的bean,SpringCloud自己负责注册这些bean,并让这些bean负责不同的功能。

​   我们也可以简单去看看invokeBeanFactoryPostProcessors方法:

​   我们可以进到invokeBeanFactoryPostProcessors方法内看看回调的逻辑:

​   我们再来看看此时容器中已经实例化的BeanFacoryPostProcessor类型的bean:

​   这三个类型的后处理器中,CachingMetadataReaderFactoryPostProcessorConfigurationWarningsPostProcessor都实现了BeanDefinitionRegistryPostProcessor接口,我们来看看它们的postProcessBeanDefinitionRegistry方法。

​   我们聚焦于核心流程,来看看CachingMetadataReaderFactoryPostProcessorpostProcessBeanDefinitionRegistry方法:

​   回调完后,我们继续回到invokeBeanFactoryPostProcessors方法中:

​   我们查看beanFactory中的beanDefinitionMap 属性可以发现org.springframework.context.annotation.internalConfigurationAnnotationProcessor 对应的bean为ConfigurationClassPostProcessor

​   接着继续回调ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry方法:

​   我们可以继续进到processConfigBeanDefinitions方法看看(这个方法巨长,我只截了关键代码):

​   所以在SpringCloud中,加载beanDefinition的主要工作是在ConfigurationClassPostProcessor中完成的。

小结

本文主要内容是SpringCloud的启动流程源码分析,主要是聚焦于与Spring启动流程的差异性和特殊方面,有关Spring启动流程源码在之前的博客中已经有过讲解,因此就没有在本文中赘述。

本文并未涉及SpringCloud服务发现和服务注册的源码,该部分会在后面的博客中更新(最近上班当牛马,狠狠干活,更新可能比较慢)。

相关推荐
Victor35618 分钟前
Hibernate(42)在Hibernate中如何实现分页?
后端
Victor35624 分钟前
Hibernate(41)Hibernate的延迟加载和急加载的区别是什么?
后端
猪猪拆迁队37 分钟前
2025年终总结-都在喊前端已死,这一年我的焦虑、挣扎与重组:AI 时代如何摆正自己的位置
前端·后端·ai编程
程序员小白条39 分钟前
面试 Java 基础八股文十问十答第八期
java·开发语言·数据库·spring·面试·职场和发展·毕设
ConardLi44 分钟前
SFT、RAG 调优效率翻倍!垂直领域大模型评估实战指南
前端·javascript·后端
Hooray2 小时前
2026年,站在职业生涯十字路口的我该何去何从?
前端·后端
唐叔在学习2 小时前
还在申请云服务器来传输数据嘛?试试P2P直连吧
后端·python
开心猴爷2 小时前
iOS 代码混淆在项目中的方式, IPA 级保护实践记录
后端
魅影骑士00102 小时前
柯里化函数
后端·设计模式
JOEH603 小时前
🛡️ 微服务雪崩救星:Sentinel 限流熔断实战,3行代码搞定高可用!
后端·全栈