Spring 源码入门篇

Spring 源码通俗解读:核心逻辑+流程拆解

Spring 源码看似复杂,但核心围绕 “IoC 容器” 和 “AOP 增强” 两大核心,再加上 Spring Boot 的 “自动化配置”,本质是一套 “帮你管理对象、简化开发” 的框架。下面结合文章中的源码细节,用 “工厂造车”“装修房子” 这样的通俗比喻,拆解核心逻辑。

一、先搞懂 Spring 的核心架构:像一个 “功能齐全的工具箱”

Spring 的整体架构可以分为 5 大核心模块,每个模块各司其职,就像工具箱里的不同工具:

模块核心作用通俗比喻
核心容器(Beans/Core/Context)管理 Bean 的创建、配置、依赖注入(IoC 核心)工厂的 “总控台”
数据访问(JDBC/ORM/Transaction)简化数据库操作、事务管理(比如整合 MyBatis、Hibernate)数据库 “连接器”
Web 模块(Web/MVC)支持 Web 开发,MVC 模式实现(接收请求、处理逻辑、返回响应)Web 开发 “脚手架”
AOP/Aspects无侵入式增强功能(比如日志、权限、事务)给房子 “装隐形设备”
测试(Test)简化 Spring 程序测试(比如 @SpringBootTest)产品 “质检工具”

其中最核心的是 “核心容器”—— 所有功能都依赖容器对 Bean 的管理,我们先从容器的核心逻辑入手。

二、IoC 容器:Spring 的 “对象大管家”

IoC(控制反转)是 Spring 的灵魂,通俗说就是 “你不用自己 new 对象,交给 Spring 容器来创建、管理,还帮你自动注入依赖”。比如你需要一个 UserService,不用写 new UserService(),容器会直接给你 “现成的”,还会自动把它依赖的 UserMapper 也注入进去。

2.1 容器的核心角色:3 个关键 “岗位”

Spring 容器的工作,靠 3 个核心类协作完成(对应文章中的源码重点):

  • DefaultListableBeanFactory:“大管家”—— 容器的核心实现,负责 Bean 的注册、存储、查找(相当于工厂的 “仓库 + 调度中心”)。
  • XmlBeanDefinitionReader:“配置解析员”—— 负责读取 XML 配置文件(比如 applicationContext.xml),把配置转换成容器能识别的 “Bean 定义”(BeanDefinition)。
  • BeanDefinition:“产品设计图”—— 存储 Bean 的所有配置信息(比如 class 路径、scope、init-method、依赖的属性),容器根据这个 “图纸” 造 Bean。

2.2 容器创建 Bean 的完整流程:从 “读配置” 到 “交成品”

就像工厂造车(先找图纸→解析图纸→备零件→组装→质检),Spring 创建 Bean 的流程也分 5 步,每一步都对应源码中的核心逻辑:

步骤 1:封装配置资源(找 “图纸”)

你写的 XML 配置文件(比如 applicationContext.xml),Spring 会先通过 ClassPathResource 封装成 “资源对象”(Resource)—— 不管文件在 classpath、本地磁盘还是网络,都统一成 Spring 能识别的格式(文章中提到的 Resource 接口及子类,就是干这个的)。

步骤 2:解析配置成 BeanDefinition(读 “图纸”)

“配置解析员”(XmlBeanDefinitionReader)会做 3 件事:

  1. 把 Resource 转换成 XML 文档(Document);
  2. 解析 XML 中的 <bean> 标签(还有 import、alias 等),提取 class、id、依赖等信息;
  3. 把这些信息封装成 BeanDefinition(“设计图”),交给 “大管家”(DefaultListableBeanFactory)注册。

源码中关键代码:

java
复制代码
// 加载XML配置,核心是解析成BeanDefinition XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml")); // 底层调用:reader.loadBeanDefinitions(resource) 完成解析和注册

步骤 3:处理依赖(备 “零件”)

如果 BeanA 依赖 BeanB(比如 UserService 依赖 UserMapper),容器会先检查 BeanB 是否已经创建:

  • 若没创建,先递归创建 BeanB;
  • 若已创建,直接拿过来用(文章中提到的 “寻找依赖” 步骤,就是解决这个问题)。

步骤 4:实例化 Bean(组装 “车子”)

容器根据 BeanDefinition 的 “设计图”,通过反射创建 Bean 实例:

  • 无参构造函数:直接 class.newInstance()
  • 有参构造函数:先解析构造函数参数,匹配对应的参数类型和值(文章中 autowireConstructor 方法就是处理有参构造的注入);
  • 特殊情况:如果用了 lookup-method(获取器注入)或 replace-method(方法替换),会通过动态代理创建实例(而非直接反射)。

步骤 5:初始化 Bean(“车子” 质检 + 升级)

实例化后,容器会做 3 件事完成 “初始化”:

  1. 注入属性:把依赖的 Bean、配置的属性(比如 <property> 标签)填充到实例中(文章中 populateBean 方法);
  2. 执行初始化方法:先执行 InitializingBean 接口的 afterPropertiesSet,再执行配置的 init-method
  3. 应用后处理器:通过 BeanPostProcessor 增强 Bean(比如 AOP 就是在这里动态织入增强逻辑)。

最终:容器缓存 Bean(“车子” 入库)

单例 Bean(默认)会被缓存到 DefaultListableBeanFactorysingletonObjects(单例缓存池)中,后续获取时直接从缓存拿,不用重复创建;原型 Bean(scope="prototype")则每次获取都新创建。

三、循环依赖:Spring 怎么解决 “互相依赖” 的死结?

比如 “BeanA 依赖 BeanB,BeanB 又依赖 BeanA”,就像 “你要先有房子才能装修,却要先装修才能有房子”——Spring 只解决单例模式下的 setter 注入循环依赖,构造器注入和原型模式无法解决。

核心思路:提前暴露 “半成品 Bean”

文章中提到的 earlySingletonExposure(提早曝光单例)机制,通俗说就是:

  1. BeanA 实例化后(还没注入属性、初始化),先把它包装成 ObjectFactory(“半成品工厂”),放到 singletonFactories 缓存中;
  2. BeanA 需要注入 BeanB,容器去创建 BeanB;
  3. BeanB 需要注入 BeanA,容器从 singletonFactories 中拿到 BeanA 的 “半成品”,注入给 BeanB;
  4. BeanB 创建完成后,注入给 BeanA,BeanA 继续完成属性注入和初始化 —— 最终两者都创建成功。

源码中关键判断:

java
复制代码
// 单例+允许循环依赖+Bean正在创建中 → 提前暴露ObjectFactory boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReference && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // 把Bean的创建工厂放入缓存,供其他Bean依赖时获取 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); }

为什么构造器循环依赖无法解决?

如果 BeanA 的构造函数需要 BeanB,BeanB 的构造函数需要 BeanA:

  1. 容器创建 BeanA 时,发现构造函数需要 BeanB,先去创建 BeanB;
  2. 创建 BeanB 时,发现构造函数需要 BeanA,又去创建 BeanA;
  3. 陷入无限循环,容器只能抛出 BeanCurrentlyInCreationException 异常(文章中提到 “构造器循环依赖无法解决”)。

四、AOP:无侵入式 “增强” 功能

AOP(面向切面编程)的核心是 “不修改原代码,给方法添加额外功能”,比如日志记录、权限校验、事务管理。就像给房子装 “隐形监控”—— 不破坏装修,却能实现监控功能。

4.1 AOP 的核心概念(通俗解释)

概念源码对应通俗比喻
切面(Aspect)@Aspect 注解的类监控系统(包含多个功能)
切点(Pointcut)@Pointcut 注解的表达式监控的目标(比如 “所有 controller 方法”)
通知(Advice)@Before/@After/@Around 等监控的动作(比如 “方法执行前记录日志”)
代理(Proxy)动态生成的代理类包装后的 “增强对象”(比如给原 Bean 套一层 “增强壳”)

4.2 AOP 的实现流程:从 “识别切面” 到 “生成代理”

  1. 开启 AOP:配置 <aop:aspectj-autoproxy /> 或 @EnableAspectJAutoProxy,Spring 会自动注册 AnnotationAwareAspectJAutoProxyCreator(AOP 的 “核心处理器”);
  2. 扫描切面:容器启动时,AnnotationAwareAspectJAutoProxyCreator 会扫描所有 @Aspect 注解的类,解析切点和通知;
  3. 匹配目标 Bean:判断哪些 Bean 的方法符合切点表达式(比如所有带 @Controller 的类的方法);
  4. 生成代理对象:对匹配的 Bean,Spring 会创建代理对象(JDK 动态代理或 CGLIB 代理):
    • JDK 动态代理:如果 Bean 实现了接口,用 JDK 的 Proxy 类生成代理(代理对象是接口的实现类);
    • CGLIB 代理:如果 Bean 没实现接口,用 CGLIB 生成代理(代理对象是原 Bean 的子类);
  5. 执行增强逻辑:调用 Bean 的方法时,实际调用的是代理对象的方法 —— 先执行通知逻辑(比如 @Before),再执行原方法(文章中提到 “AOP 通过 BeanPostProcessor 在初始化后生成代理”)。

五、Spring Boot 自动化配置:为什么 “引入依赖就能用”?

Spring Boot 的核心是 “约定大于配置”,比如引入 spring-boot-starter-web 就能自动配置 Tomcat、DispatcherServlet,不用写一行 XML。这背后的核心是 @EnableAutoConfiguration 注解。

自动化配置的 3 个关键步骤:

  1. 开启自动配置:@SpringBootApplication 注解包含 @EnableAutoConfiguration,相当于 “打开自动配置开关”;
  2. 扫描配置文件:@EnableAutoConfiguration 会导入 AutoConfigurationImportSelector,这个类会扫描 classpath 下所有 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件(文章中提到 “AutoConfigurationImportSelector 是配置扫描器”);
  3. 条件化加载:每个自动配置类(比如 TomcatAutoConfiguration)都有 @ConditionalOnClass(存在某个类才生效)、@ConditionalOnMissingBean(不存在某个 Bean 才生效)等条件 —— 只有满足条件,才会自动配置对应的 Bean。

比如引入 spring-boot-starter-web 后:

  • 依赖中包含 Tomcat 和 Spring MVC 的类;
  • TomcatAutoConfiguration 满足 @ConditionalOnClass(Tomcat.class),自动创建 Tomcat 容器;
  • DispatcherServletAutoConfiguration 满足条件,自动创建 DispatcherServlet(Spring MVC 的核心控制器);
  • 最终你不用配置 Tomcat 和 DispatcherServlet,直接写 Controller 就能接收请求。

六、Spring 源码核心思想总结

  1. IoC 容器:核心是 “反转控制”—— 从 “你创建对象” 变成 “容器给你对象”,解决了对象依赖混乱的问题(比如不用手动 new 依赖的对象);
  2. 依赖注入:容器根据 BeanDefinition,自动把依赖的 Bean 注入到目标 Bean 中,实现 “松耦合”;
  3. AOP 增强:无侵入式添加功能,把日志、权限等通用逻辑和业务逻辑分离,提高代码复用性;
  4. 约定大于配置:Spring Boot 通过 “约定默认配置”(比如默认端口 8080、默认配置文件名称),减少开发者的配置工作量;
  5. 高扩展性:通过 BeanPostProcessor(Bean 后处理器)、FactoryBean(工厂 Bean)、自定义标签等,允许开发者扩展 Spring 的功能(文章中提到 “Spring 的开放式架构让扩展变得简单”)。
0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
她和夏天一样热
作者分享
工厂方法模式简介及应用场景
3
策略模式简介及应用场景
5
入职dayone 刚开始还庆幸这家还好是纯springboot那一套框架开发, 但是进来发现整个开发部就四个人,而且目前这个项目完全是由实习生自己开发的,而且是口头给需求,感觉有点小压力, 毕竟之前没实习过,基本没人带,现在就只能靠另外一个实习的哥们帮忙带一下(他在里面干了三四个月好像也快离开了) 而且今天给了一个小需求,然后要我们自己设计新的表😂 目前想法就是先顶住压力干三个月 后面再找找机会 或者等明年春招,这个思路可以嘛? 还有 问一下已经有过实习经历的友友,有没有建议给到我刚入职的萌新, 像给我一个需求 ,我是先去捋一下思路,需要设计表,哪些字段,还有返回给前端什么数据这些 ,是不是得先想好啊, 项目负责人都没告诉我,就是直接口头给需求 然后我们开发就行😭 现在脑子懵,希望有经验的友友给点建议,平时拿到一个需求是怎么开发的呢
3
用宝塔面板部署的项目,现在报连接不上redis 是什么原因呢?我本身项目代码是没问题的 在本地能正常连接,但是怎么连不上宝塔上面下的reids呢?之前还可以 然后后面项目线上报错我就重新删了再搞 结果就报连接不是redis了,配置密码也没问题呀。有大佬有遇到这种问题嘛,求助!
2
#云图库扩展点 #AI上传优化为消息队列异步执行 有大佬已经完成了这个拓展点了吗,求助!!! 消息队列是这两天看Bi项目的文档笔记现学的。 然后拉bi项目的源码看。搞了一个早上,思路很乱,不清楚生产者跟消费者具体要传什么message,晕了 先吃个饭 下午再整理一下思路,希望有大佬能指点一下😭
4
下载 APP