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

简介: 今天给大家带来的是一篇源码解析文章,关于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

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

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的属性进行解析,然后注入相应的依赖。

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

至此,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可以查看这个类的继承关系。对于梳理源码中类与类之间的关系非常有用。在图里还可以添加和删除类,定制化展示我们关注的类关系。

不要陷入细节

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

目录
相关文章
|
缓存 安全 Java
Spring Security通用权限管理模型解析
Spring Security作为Spring生态的核心安全框架,结合RBAC与ACL权限模型,基于IoC与AOP构建灵活、可扩展的企业级权限控制体系,涵盖认证、授权流程及数据库设计、性能优化等实现策略。
891 0
|
缓存 安全 Java
Spring Security权限管理解析
Spring Security是Spring生态中的核心安全框架,采用认证与授权分离架构,提供高度可定制的权限管理方案。其基于过滤器链实现认证流程,通过SecurityContextHolder管理用户状态,并结合RBAC模型与动态权限决策,支持细粒度访问控制。通过扩展点如自定义投票器、注解式校验与前端标签,可灵活适配多租户、API网关等复杂场景。结合缓存优化与无状态设计,适用于高并发与前后端分离架构。
832 0
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
3123 0
|
12月前
|
XML Java 数据格式
《深入理解Spring》:AOP面向切面编程深度解析
Spring AOP通过代理模式实现面向切面编程,将日志、事务等横切关注点与业务逻辑分离。支持注解、XML和编程式配置,提供五种通知类型及丰富切点表达式,助力构建高内聚、低耦合的可维护系统。
|
12月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
12月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。
|
Java 数据库 数据安全/隐私保护
Spring Boot四层架构深度解析
本文详解Spring Boot四层架构(Controller-Service-DAO-Database)的核心思想与实战应用,涵盖职责划分、代码结构、依赖注入、事务管理及常见问题解决方案,助力构建高内聚、低耦合的企业级应用。
2021 1
|
Kubernetes Java 微服务
Spring Cloud 微服务架构技术解析与实践指南
本文档全面介绍 Spring Cloud 微服务架构的核心组件、设计理念和实现方案。作为构建分布式系统的综合工具箱,Spring Cloud 为微服务架构提供了服务发现、配置管理、负载均衡、熔断器等关键功能的标准化实现。本文将深入探讨其核心组件的工作原理、集成方式以及在实际项目中的最佳实践,帮助开发者构建高可用、可扩展的分布式系统。
1004 0
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
1564 0
|
人工智能 Java 测试技术
Spring Boot 集成 JUnit 单元测试
本文介绍了在Spring Boot中使用JUnit 5进行单元测试的常用方法与技巧,包括添加依赖、编写测试类、使用@SpringBootTest参数、自动装配测试模块(如JSON、MVC、WebFlux、JDBC等),以及@MockBean和@SpyBean的应用。内容实用,适合Java开发者参考学习。
1513 0

推荐镜像

更多
  • DNS