源码解析 - Spring如何实现IoC的?

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 今天给大家带来的是一篇源码解析文章,关于Spring IoC的。其实源码解析不太适合写文章,做成视频更好,因为代码比较多,而且繁杂,而且调用链长,用图文很难写清楚,我尽量把它写得清楚一点~

荒腔走板


上周一冲动买了个游戏手柄。

网络异常,图片无法展示
|

小时候很喜欢玩游戏,那个时候手柄游戏还是插卡的,冒险岛和魂斗罗什么的。后来接触了电脑游戏,就很少玩手柄游戏了。


之前下载了一个《古剑奇谭3》,用键盘玩了一阵子,可能是我手残吧,始终感觉反应不过来,所以想买个手柄看能不能好一点。


最近在Steam上趁着打折买了巫师3,有朋友说,贵的不是买游戏的钱,而是时间。是的,工作以后基本上都很忙了,业余时间总是觉得不太够用,可以用来玩游戏的时间实在是太少了。这个巫师3估计够我玩一年了~


不过买手柄也是为了玩点比较轻松的游戏,工作之余适当放松一下还是有必要的,适度就好。


今天给大家带来的是一篇源码解析文章,关于Spring IoC的。其实源码解析不太适合写文章,做成视频更好,因为代码比较多,而且繁杂,而且调用链长,用图文很难写清楚,我尽量把它写得清楚一点~


本文所使用版本是SpringBoot 2.3.3.RELEASE,对应的Spring版本是5.2.8.RELEASE。


从Debug开始

一般来说,源码解析分为几个途径:直接读代码;分析类之间的关系,画UML图;Debug走一遍程序。

有时候源码可能比较复杂,比如Spring这种,链路比较长的,如果直接看代码是比较困难的,我们可以从Debug开始。

本文由于是源码解析文章,所以直接看可能有点费力,强烈推荐跟着文章一起Debug!!!

所以为了写这篇文章,我新建了一个空的SpringBoot项目,然后在启动类打了一个断点,Debug走起:

网络异常,图片无法展示
|

Debug一路往下走,会看到SpringApplication类的run方法里面,有一个创建ApplicationContext的操作:

网络异常,图片无法展示
|

当然了,在那之前有一些设置环境和Banner的操作。那这个ApplicationContext是什么东西呢?通过debug窗口我们可以看到创建的是一个AnnotationConfigServletWebServerApplicationContext实例。看看它的类图:

网络异常,图片无法展示
|


ApplicationContext

在上面的类图里,我们关注里面两个比较重要的接口:BeanFactoryApplicationContext

BeanFactory顾名思义,是一个工厂类,提供了一些获取Bean的方法,是IoC容器最基本的接口。而ApplicationContext接口继承了BeanFactory,另外通过继承其它接口赋予了更多的特性,可以看成是更高级的容器。我们从Debug也可以看到,Spring在启动的时候,创建了一个ApplicationContext,然后通过这个ApplicationContext对Bean进行后续的操作。


refresh

继续Debug,往下面走几行可以看到对之前创建的context进行了一个refresh的操作。进去后发现其实就是调用的这个context的refresh()方法。而这个方法的主要实现是在一个抽象类AbstractApplicationContext里面。

网络异常,图片无法展示
|

这个方法的代码看起来比较简单干净,是一系列的方法调用。这些方法都是protected修饰的,基本上交给子类去实现了。这里是一个比较典型的模板方法设计模式的应用。

每个方法上面都有英文的注释,说明这个方法是用来干嘛的,我这里就不翻译一遍了。不过我们需要关注的是第二个方法(533行,图中我打bookmark的地方),和第三个方法(536行,图中我打Debug断点的地方)。

这里顺便提一下Idea的bookmark功能,阅读源码的时候非常有用。你可以在你觉得比较重要的地方设置一个标记,这样以后就可以很方便地随时回到那个地方。在windows下,打一个普通的bookmark是F11键,而如果想给这个bookmark一个编号,可以按住shift + F11,可以给它打上一个数字或者一个字母用来特殊标识。如果是一个数字,比如3,那你在任何地方按ctrl + 3就可以跳回到这个打标记的地方。


注册Bean

注册Bean是通过上面提到的在refresh的一系列方法中的第二个方法来实现的:

invokeBeanFactoryPostProcessors(beanFactory);

Debug进去,发现使用了一个PostProcessorRegistrationDelegate类的静态方法invokeBeanFactoryPostProcessors。这个方法的代码很长,我们直接来到最关键的地方:

网络异常,图片无法展示
|

在ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法里面,通过最后一行方法进去processConfigBeanDefinitions方法。

这个方法的作用是找到配置类的入口。由于我是直接使用的SpringBoot,所以配置类入口只有我们自己定义的启动类SpringBaseApplication

在这个方法内部可以找到这样一行代码:

parser.parse(candidates);

注意这里的Candidates就是我们的Configuration类。

继续Debug进去,可以从下图看到它是会先判断这个candidate是不是一个用注解定义的Bean。而@Configuration注解本身是被@Component注解修饰了的,所以它是一个被注解修饰的Bean。

网络异常,图片无法展示
|

这里有一个BeanDefinition类,它是Spring用来包装和修饰一个Bean的数据结构,包括Bean的依赖、scope等等很多信息。

我们继续往下Debug,来到了ConfigurationClassParser类的processConfigurationClass方法。也是直接找到最关键的代码:

网络异常,图片无法展示
|

然后进去,发现又是一个代码非常多的方法。不要着急,我们仍然是看最关键的代码:

网络异常,图片无法展示
|

可以看到,这里取了@ComponentScan@ComponentScans注解,也就是我们会在配置类上面配置Spring应该去哪些包扫描Bean的。Spring就是在这个地方取出来的。取出来以后,从294行Debug进去看看它是怎么解析的:

网络异常,图片无法展示
|

这个方法内部比较简单,就是找到basePackage,然后scan。如果我们在@ComponantScan里面定义了basePackage,它就用我们定义的。有过SpringBoot使用经验的同学可能会知道,如果我们不用定义basePackage,Spring默认会扫描启动类所在包及其子包下的Bean。这是怎么实现的呢?原来,@SpringBootApplication会被@ComponentScan修饰,而它没有定义basePackage,那么Spring就会在上图中第123行代码,通过反射取得启动类所在的包,加入到basePackage里面。

然后我们继续通过第132行进入doScan方法。

这个方法内部就是用传进来的basePackage,通过扫描class获取到相应的BeanDefinition。然后循环去处理每个BeanDefinition,通过调用registerBeanDefinition方法,去注册Bean。

我们从registerBeanDefinition方法进去,发现最后走到了DefaultListableBeanFactory类的registerBeanDefinition方法里面。在这个方法里,Spring会先通过beanName尝试从this.beanDefinitionMap里取出一个BeanDefinition,如果没有,会把它put进这个map里。至此,Spring注册Bean的流程就算结束了。


初始化Bean

注册完Bean,只是把Bean交给Spring管理了,但这个时候Bean还没有初始化。我们回到最开始的AbstractApplicationContext类里面的那一系列模板方法的地方。下一个方法就是去初始化Bean。

registerBeanPostProcessors(beanFactory);

按照惯例,一路Debug进去,在PostProcessorRegistrationDelegate类的registerBeanPostProcessors方法里面,可以看到这里对我们定义的Bean,执行了一个beanFactory.getBean操作。而这个操作就是尝试去从Spring中拿一个Bean。如果这个Bean还没有初始化,Spring会进行初始化和依赖注入操作。

网络异常,图片无法展示
|

Debug进去,会发现主要逻辑是在AbstractBeanFactory类的doGetBean方法里面实现的。我们这个时候,我们用户定义的Bean还没有初始化,所以会走初始化流程。继续Debug,会发现这是通过doCreateBean方法来实现的。

网络异常,图片无法展示
|

在doCreateBean里面,会使用createBeanInstance创建Bean,如果发现有依赖,会通过populateBean方法来处理依赖。

在创建Bean的过程,会依次尝试使用工厂方法、构造函数、反射的方式来实现Bean的实例化。

在处理依赖过程,如果发现有依赖,会通过依赖的beanName调用getBean方法,这样就形成了一个递归调用(如果依赖又有其它依赖)。最后通过applyPropertyValues方法,对Bean的属性进行解析,然后注入相应的依赖。

如果是属性注入,底层是使用的BeanWapperImplsetValue方法,它是基于反射来实现的。

至此,Spring就完成了Bean的扫描、注册、实例化的整个过程。后面就可以通过ApplicationContext来获取Bean实例了。

当然,Spring的IoC做的非常完善,Bean的生命周期和扩展、如何解决循环依赖等等,都是有相应的代码来实现。这里只介绍了主线的从Spring启动到实例化Bean的过程,如果读者朋友对更多的细节感兴趣,可以自己去Debug多看看其它分支。


源码解析技巧

文章写完了,感觉源码解析类的文章还是蛮难写的,主要自己得理一遍完整的流程还是比较花时间。这里介绍一些源码解析的小技巧。

Debug

Debug是非常有用的,因为它能够直观地得到很多运行时信息。比如Spring的设计非常复杂,用到了很多设计模式,有很多接口和继承关系。如果不Debug,只是看代码的话,无法直观的看到这个地方的实现类是什么,想要点进去看发现有十几个实现类,一下子就懵逼了。

Debug的话,要把快捷键记熟练。这里推荐两个在Idea不常用但很实用的功能吧。

一个是F9,使用F9可以直接跳到下一个断点。而使用Alt + F9可以直接跳到光标所在的行。

另一个右键点击断点,可以给断点设置条件。这在循环里面非常有用,可以直接跳到你想要的那个条件下的地方。

bookmark

bookmark可以标记代码。我们在读源码的时候,很容易跳过去跳过来。如果不用标记的话,可能很快就找不到地方了。用了标记可以帮助我们记忆比较重要的代码,也可以快速跳转。

类图

在一个类里面点击右键,选择Diagrams -> Show Diagram可以查看这个类的继承关系。对于梳理源码中类与类之间的关系非常有用。在图里还可以添加和删除类,定制化展示我们关注的类关系。

不要陷入细节

陷入细节是读源码时非常容易碰到的一个误区。很多人觉得读源码很难,看不懂,可能就是太过于陷入细节。其实我们研究源码不一定要每一行都看懂,只要看懂它主要的实现逻辑就行了,对于我们感兴趣的,可以在后面再深入进去看,这样的话就有了一个宏观的视角,才能理清楚整个设计思路,来龙去脉。

目录
相关文章
|
1天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
1天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
1天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
1天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
25天前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
52 12
|
20天前
|
PyTorch Shell API
Ascend Extension for PyTorch的源码解析
本文介绍了Ascend对PyTorch代码的适配过程,包括源码下载、编译步骤及常见问题,详细解析了torch-npu编译后的文件结构和三种实现昇腾NPU算子调用的方式:通过torch的register方式、定义算子方式和API重定向映射方式。这对于开发者理解和使用Ascend平台上的PyTorch具有重要指导意义。
|
21天前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
41 2
|
2天前
|
安全 搜索推荐 数据挖掘
陪玩系统源码开发流程解析,成品陪玩系统源码的优点
我们自主开发的多客陪玩系统源码,整合了市面上主流陪玩APP功能,支持二次开发。该系统适用于线上游戏陪玩、语音视频聊天、心理咨询等场景,提供用户注册管理、陪玩者资料库、预约匹配、实时通讯、支付结算、安全隐私保护、客户服务及数据分析等功能,打造综合性社交平台。随着互联网技术发展,陪玩系统正成为游戏爱好者的新宠,改变游戏体验并带来新的商业模式。
|
26天前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
90 2
|
26天前
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
42 2

推荐镜像

更多