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 件事:
- 把 Resource 转换成 XML 文档(Document);
- 解析 XML 中的
<bean>标签(还有 import、alias 等),提取 class、id、依赖等信息; - 把这些信息封装成 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 件事完成 “初始化”:
- 注入属性:把依赖的 Bean、配置的属性(比如
<property>标签)填充到实例中(文章中populateBean方法); - 执行初始化方法:先执行
InitializingBean接口的afterPropertiesSet,再执行配置的init-method; - 应用后处理器:通过
BeanPostProcessor增强 Bean(比如 AOP 就是在这里动态织入增强逻辑)。
最终:容器缓存 Bean(“车子” 入库)
单例 Bean(默认)会被缓存到 DefaultListableBeanFactory 的 singletonObjects(单例缓存池)中,后续获取时直接从缓存拿,不用重复创建;原型 Bean(scope="prototype")则每次获取都新创建。
三、循环依赖:Spring 怎么解决 “互相依赖” 的死结?
比如 “BeanA 依赖 BeanB,BeanB 又依赖 BeanA”,就像 “你要先有房子才能装修,却要先装修才能有房子”——Spring 只解决单例模式下的 setter 注入循环依赖,构造器注入和原型模式无法解决。
核心思路:提前暴露 “半成品 Bean”
文章中提到的 earlySingletonExposure(提早曝光单例)机制,通俗说就是:
- BeanA 实例化后(还没注入属性、初始化),先把它包装成
ObjectFactory(“半成品工厂”),放到singletonFactories缓存中; - BeanA 需要注入 BeanB,容器去创建 BeanB;
- BeanB 需要注入 BeanA,容器从
singletonFactories中拿到 BeanA 的 “半成品”,注入给 BeanB; - 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:
- 容器创建 BeanA 时,发现构造函数需要 BeanB,先去创建 BeanB;
- 创建 BeanB 时,发现构造函数需要 BeanA,又去创建 BeanA;
- 陷入无限循环,容器只能抛出
BeanCurrentlyInCreationException异常(文章中提到 “构造器循环依赖无法解决”)。
四、AOP:无侵入式 “增强” 功能
AOP(面向切面编程)的核心是 “不修改原代码,给方法添加额外功能”,比如日志记录、权限校验、事务管理。就像给房子装 “隐形监控”—— 不破坏装修,却能实现监控功能。
4.1 AOP 的核心概念(通俗解释)
| 概念 | 源码对应 | 通俗比喻 |
|---|---|---|
| 切面(Aspect) | @Aspect 注解的类 | 监控系统(包含多个功能) |
| 切点(Pointcut) | @Pointcut 注解的表达式 | 监控的目标(比如 “所有 controller 方法”) |
| 通知(Advice) | @Before/@After/@Around 等 | 监控的动作(比如 “方法执行前记录日志”) |
| 代理(Proxy) | 动态生成的代理类 | 包装后的 “增强对象”(比如给原 Bean 套一层 “增强壳”) |
4.2 AOP 的实现流程:从 “识别切面” 到 “生成代理”
- 开启 AOP:配置
<aop:aspectj-autoproxy />或 @EnableAspectJAutoProxy,Spring 会自动注册AnnotationAwareAspectJAutoProxyCreator(AOP 的 “核心处理器”); - 扫描切面:容器启动时,
AnnotationAwareAspectJAutoProxyCreator会扫描所有 @Aspect 注解的类,解析切点和通知; - 匹配目标 Bean:判断哪些 Bean 的方法符合切点表达式(比如所有带 @Controller 的类的方法);
- 生成代理对象:对匹配的 Bean,Spring 会创建代理对象(JDK 动态代理或 CGLIB 代理):
- JDK 动态代理:如果 Bean 实现了接口,用 JDK 的
Proxy类生成代理(代理对象是接口的实现类); - CGLIB 代理:如果 Bean 没实现接口,用 CGLIB 生成代理(代理对象是原 Bean 的子类);
- JDK 动态代理:如果 Bean 实现了接口,用 JDK 的
- 执行增强逻辑:调用 Bean 的方法时,实际调用的是代理对象的方法 —— 先执行通知逻辑(比如 @Before),再执行原方法(文章中提到 “AOP 通过 BeanPostProcessor 在初始化后生成代理”)。
五、Spring Boot 自动化配置:为什么 “引入依赖就能用”?
Spring Boot 的核心是 “约定大于配置”,比如引入 spring-boot-starter-web 就能自动配置 Tomcat、DispatcherServlet,不用写一行 XML。这背后的核心是 @EnableAutoConfiguration 注解。
自动化配置的 3 个关键步骤:
- 开启自动配置:@SpringBootApplication 注解包含 @EnableAutoConfiguration,相当于 “打开自动配置开关”;
- 扫描配置文件:@EnableAutoConfiguration 会导入
AutoConfigurationImportSelector,这个类会扫描 classpath 下所有META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件(文章中提到 “AutoConfigurationImportSelector 是配置扫描器”); - 条件化加载:每个自动配置类(比如
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 源码核心思想总结
- IoC 容器:核心是 “反转控制”—— 从 “你创建对象” 变成 “容器给你对象”,解决了对象依赖混乱的问题(比如不用手动 new 依赖的对象);
- 依赖注入:容器根据 BeanDefinition,自动把依赖的 Bean 注入到目标 Bean 中,实现 “松耦合”;
- AOP 增强:无侵入式添加功能,把日志、权限等通用逻辑和业务逻辑分离,提高代码复用性;
- 约定大于配置:Spring Boot 通过 “约定默认配置”(比如默认端口 8080、默认配置文件名称),减少开发者的配置工作量;
- 高扩展性:通过
BeanPostProcessor(Bean 后处理器)、FactoryBean(工厂 Bean)、自定义标签等,允许开发者扩展 Spring 的功能(文章中提到 “Spring 的开放式架构让扩展变得简单”)。
