很多人学 Spring,容易停留在“会用”的阶段。知道 @Autowired 能注入,知道 @Transactional 能开事务,知道 AOP 能做增强,但一旦面试官开始往源码里追,比如问 BeanDefinition 是什么、refresh 到底做了什么、AOP 为什么靠 BeanPostProcessor、事务为什么本质还是基于 Connection,你就会发现这些知识点如果不串起来,很容易散。
Spring 源码看起来很大,但真正抓主线,核心问题其实就三个:
- 定义从哪来?
- 对象怎么造?
- 增强怎么织?
我觉得 Spring 源码真正的主线其实只有两条:
- 第一条是 IOC 主线:Spring 怎么把配置变成 BeanDefinition,再把 BeanDefinition 变成真正可用的 Bean。
- 第二条是 AOP 主线:Spring 怎么在不改业务代码的前提下,把代理、事务、日志这些横切逻辑织进去。而事务,本质上就是建在 AOP 之上的一个典型应用。
Spring 源码的第一条主线,是 IOC:
- 先把配置解析成 BeanDefinition。
- 再由 BeanFactory / ApplicationContext 托管。
- 通过
register和refresh把容器跑起来。 - 最后由
getBean走实例化、注入、初始化,把定义变成真正的 Bean。
在这个过程中,BeanDefinitionRegistryPostProcessor / BeanFactoryPostProcessor / BeanPostProcessor 这三类后置处理器,分别把扩展点插在了“定义注册前后”和“对象初始化前后”。
Spring 源码的第二条主线,是 AOP:
- AOP 借助 BeanPostProcessor 在 Bean 初始化阶段织入。
- Spring 根据 Advisor 和 Pointcut 做方法匹配。
- 然后组装拦截器链,最终生成代理对象。
- 声明式事务只是这套 AOP 代理机制上的一个典型应用。
从 IOC 容器的最小单位讲起
Spring 里最容易被问懵的一个点,是 BeanDefinition 和 Bean 到底有什么区别。
很多人一听 Bean 就觉得是对象,其实在 Spring 里,真正先出现的不是 Bean,而是 BeanDefinition。你可以把 BeanDefinition 理解成“对象的施工图纸”,里面记录了类名、作用域、初始化方法、依赖关系这些元数据。但它还不是那个真正已经创建好的对象。
只有当 Spring 真正去实例化、注入、初始化之后,这个定义才会落成一个可用的 Bean。
相关阅读:
然后问题来了:这些 BeanDefinition 和 Bean 到底由谁来管?最底层的答案是 BeanFactory。BeanFactory 是 Spring 容器最核心的基础接口,真正负责管理 Bean 的创建、查找和生命周期。
但我们日常开发真正接触更多的,其实是 ApplicationContext。它不是推翻 BeanFactory 重新来一套,而是站在 BeanFactory 之上继续做增强。比如国际化、事件发布、资源加载、自动注册后置处理器、容器启动时预实例化非懒加载单例,这些能力都是 ApplicationContext 额外补上的。
相关阅读:
Spring 容器启动的完整流程
如果你是基于注解启动 Spring,比如 AnnotationConfigApplicationContext,那最核心的两个动作就是:
-
register:本质上是把配置类先注册进容器体系; -
refresh:这个方法几乎把 IOC 容器从无到有地跑了一遍。- 准备 BeanFactory。
- 执行 BeanFactoryPostProcessor / BeanDefinitionRegistryPostProcessor。
- 注册 BeanPostProcessor。
- 完成事件、多播器等基础设施初始化。
- 实例化所有非懒加载单例 Bean。
- 容器刷新完成。
其中最关键的两个扩展点是:
- BeanDefinitionRegistryPostProcessor:可以在 BeanDefinition 注册阶段继续往容器里塞新的 BeanDefinition。
- BeanFactoryPostProcessor:可以在 BeanDefinition 已经准备好、但 Bean 还没真正创建之前,去修改这些定义。
像 ConfigurationClassPostProcessor,就是 Spring 注解体系真正跑起来的关键。它会解析 @Configuration、@ComponentScan、@Bean、@Import 这些注解,并持续往容器里补充新的 BeanDefinition。
对应八股文:
相关阅读:
- AnnotationConfigApplicationContext 的 register 和 refresh 做了什么?
- BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor、BeanPostProcessor 有什么区别…
Bean 是怎么从定义变成对象的
容器准备好以后,接下来就是 Bean 的真正创建。这里最核心的入口通常就是 getBean()。
很多面试官会直接问你:Spring 的 getBean 核心流程是什么? 这个问题的本质是:Spring 到底是怎么把 BeanDefinition 变成 Bean 的。
- 先查缓存,尤其是单例池里有没有现成对象。
- 没有的话,就进入创建流程。
- 创建流程里通常包括实例化、属性注入、初始化。
- 如果是单例,还要把成品放回单例池缓存起来。
在源码层面,经常会看到这几个关键点:
doGetBeancreateBeandoCreateBeancreateBeanInstancepopulateBeaninitializeBean
这几个方法其实刚好对应了你在面试里最常说的那几步:创建对象、依赖注入、初始化。
相关阅读:
单例、多例、线程安全,为什么经常会被混着问
Spring 默认作用域是 singleton,这意味着同一个 BeanName 在整个容器里通常只会有一个实例,它会被缓存到单例池里,后面再拿时直接复用。
而 prototype 则完全不同。每次 getBean(),Spring 都会重新创建一个新对象,通常不会把它缓存到单例池里,也不会像单例那样完整托管它的销毁过程。
所以面试官会围绕两个层面来追:
- 底层管理差异:单例走缓存,多例基本不缓存。
- 并发语义差异:单例是否线程安全,多例是否就一定安全。
这里一定要注意,作用域和线程安全不是一个概念。单例如果是无状态的,完全可以线程安全;多例如果内部又引用了共享资源,也照样可能有并发问题。
相关阅读:
循环依赖和三级缓存,发生在 Bean 生命周期的哪一步
当 Spring 走到属性注入阶段时,就会碰到一个经典死局:A 依赖 B,B 又依赖 A。如果没有特殊机制,那 A 在等 B,B 又在等 A,整个容器就卡死了。
Spring 解决这个问题的核心思路叫提前暴露,而提前暴露的具体载体,就是三级缓存。
这三层你一定要能说清:
- 一级缓存:成品单例 Bean。
- 二级缓存:提前暴露的半成品 Bean。
- 三级缓存:能生产早期引用的工厂,尤其是给 AOP 代理留空间。
为什么不是二级缓存就够了?因为 Spring 不只是要解决“对象互相引用”,还要解决“如果这个对象未来会被 AOP 代理,那注入出去的到底是原对象还是代理对象”。
也就是说,三级缓存的价值,不只是为了解循环依赖,更是为了给早期代理对象预留扩展点。
相关阅读:
AOP 与 BeanPostProcessor
很多人学 AOP,会先记住 JDK 动态代理和 CGLIB,但真正源码层面更重要的问题是:Spring 为什么能在不改业务代码的情况下,把代理对象替换进去?
答案就在 BeanPostProcessor。
Spring AOP 和 IOC 不是平行关系,AOP 是寄生在 IOC 生命周期里的。一个 Bean 在初始化前后,会经过 BeanPostProcessor 的前后置回调,而 Spring 的自动代理创建器,像 AbstractAutoProxyCreator,本质上就是一个特殊的 BeanPostProcessor。
这意味着 Spring 可以在 postProcessAfterInitialization 阶段判断:
- 这个 Bean 要不要增强?
- 如果要增强,匹配哪些 Advisor?
- 最终返回原始对象,还是返回一个代理对象?
一旦返回的是代理对象,后续容器里流转的就是代理,而不是原始对象。
相关阅读:
接下来,才是大家熟悉的那部分:JDK 动态代理和 CGLIB。
- 如果目标类实现了接口,Spring 优先考虑 JDK 动态代理。
- 如果目标类没实现接口,或者配置要求走类代理,Spring 会使用 CGLIB。
但别只停在这里,更重要的是要知道 Spring 会先把目标对象、Advisor、Interceptor 这些信息封装起来,再通过 ProxyFactory 生成最终代理对象。
相关阅读:
切点怎么匹配?调用链怎么跑起来?
Spring AOP 不只是“生成代理”就完了,代理对象调用方法时,到底怎么知道哪些增强逻辑该执行? 这里的主线是:
- 先根据 Advisor 里的 Pointcut 去筛方法。
- 把匹配成功的增强逻辑组装成一条拦截器链。
- 最后通过 MethodInvocation 递归推进。
这也是为什么很多源码里你会看到 MethodInterceptor、ReflectiveMethodInvocation、proceed() 这些关键词。
proceed() 的本质可以理解成一句话:如果还有下一个拦截器,就继续调用下一个;如果没有了,就执行目标方法本身。
所以 AOP 的执行并不是“代理对象上来先执行目标方法,再补点逻辑”,而是构建了一条环绕式调用链,一层层包进去。理解了这一层,你再回头看 AOP 失效场景,就会特别清楚。AOP 失效本质上就是:调用链绕过了代理。
相关阅读:
事务 = AOP + Connection
Spring 事务这块,很多人背到最后会变成一堆抽象名词:@Transactional、传播行为、隔离级别、TransactionManager。
但如果你从源码主线去看,其实它非常朴素:Spring 声明式事务 = AOP 拦截 + TransactionInterceptor 增强 + PlatformTransactionManager 管连接事务状态。
也就是说,@Transactional 之所以能生效,不是因为 Spring 对方法做了什么魔法,而是因为这个方法最终也被代理了。调用进来后,事务增强逻辑会先运行:
- 看当前方法有没有事务属性。
- 根据传播行为判断是加入现有事务,还是新开事务。
- 通过
PlatformTransactionManager开启事务。 - 执行业务方法。
- 根据执行结果决定提交还是回滚。
如果底层是 JDBC / DataSource 体系,那这个事务最终一定绕不开 Connection。Spring 事务本质上还是在管理 Connection 的自动提交、提交、回滚,以及线程绑定。
相关阅读:
而事务传播行为,本质上是为了处理一个更具体的问题:事务嵌套和事务边界冲突怎么协调。
比如:
- 当前已经有事务了,新方法进来是加入还是新开?
- 外层方法有事务,内层方法要求强制新事务怎么办?
- 如果方法要求必须在事务里执行,但当前根本没事务怎么办?
这些问题最终就落到了 REQUIRED、REQUIRES_NEW、MANDATORY、SUPPORTS、NESTED 这些传播属性上。
相关阅读: