Skip to Content
面试题库Spring CoreSpring核心原理及源码

很多人学 Spring,容易停留在“会用”的阶段。知道 @Autowired 能注入,知道 @Transactional 能开事务,知道 AOP 能做增强,但一旦面试官开始往源码里追,比如问 BeanDefinition 是什么、refresh 到底做了什么、AOP 为什么靠 BeanPostProcessor、事务为什么本质还是基于 Connection,你就会发现这些知识点如果不串起来,很容易散。

Spring 源码看起来很大,但真正抓主线,核心问题其实就三个:

  1. 定义从哪来?
  2. 对象怎么造?
  3. 增强怎么织?

我觉得 Spring 源码真正的主线其实只有两条:

  • 第一条是 IOC 主线:Spring 怎么把配置变成 BeanDefinition,再把 BeanDefinition 变成真正可用的 Bean。
  • 第二条是 AOP 主线:Spring 怎么在不改业务代码的前提下,把代理、事务、日志这些横切逻辑织进去。而事务,本质上就是建在 AOP 之上的一个典型应用。

Spring 源码的第一条主线,是 IOC:

  1. 先把配置解析成 BeanDefinition
  2. 再由 BeanFactory / ApplicationContext 托管。
  3. 通过 registerrefresh 把容器跑起来。
  4. 最后由 getBean 走实例化、注入、初始化,把定义变成真正的 Bean。

在这个过程中,BeanDefinitionRegistryPostProcessor / BeanFactoryPostProcessor / BeanPostProcessor 这三类后置处理器,分别把扩展点插在了“定义注册前后”和“对象初始化前后”。

Spring 源码的第二条主线,是 AOP:

  1. AOP 借助 BeanPostProcessor 在 Bean 初始化阶段织入。
  2. Spring 根据 Advisor 和 Pointcut 做方法匹配。
  3. 然后组装拦截器链,最终生成代理对象。
  4. 声明式事务只是这套 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。

对应八股文:

相关阅读:


Bean 是怎么从定义变成对象的

容器准备好以后,接下来就是 Bean 的真正创建。这里最核心的入口通常就是 getBean()

很多面试官会直接问你:Spring 的 getBean 核心流程是什么? 这个问题的本质是:Spring 到底是怎么把 BeanDefinition 变成 Bean 的。

  1. 先查缓存,尤其是单例池里有没有现成对象。
  2. 没有的话,就进入创建流程。
  3. 创建流程里通常包括实例化、属性注入、初始化。
  4. 如果是单例,还要把成品放回单例池缓存起来。

在源码层面,经常会看到这几个关键点:

  • doGetBean
  • createBean
  • doCreateBean
  • createBeanInstance
  • populateBean
  • initializeBean

这几个方法其实刚好对应了你在面试里最常说的那几步:创建对象、依赖注入、初始化。

相关阅读:

单例、多例、线程安全,为什么经常会被混着问

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 不只是“生成代理”就完了,代理对象调用方法时,到底怎么知道哪些增强逻辑该执行? 这里的主线是:

  1. 先根据 Advisor 里的 Pointcut 去筛方法。
  2. 把匹配成功的增强逻辑组装成一条拦截器链。
  3. 最后通过 MethodInvocation 递归推进。

这也是为什么很多源码里你会看到 MethodInterceptorReflectiveMethodInvocationproceed() 这些关键词。

proceed() 的本质可以理解成一句话:如果还有下一个拦截器,就继续调用下一个;如果没有了,就执行目标方法本身。

所以 AOP 的执行并不是“代理对象上来先执行目标方法,再补点逻辑”,而是构建了一条环绕式调用链,一层层包进去。理解了这一层,你再回头看 AOP 失效场景,就会特别清楚。AOP 失效本质上就是:调用链绕过了代理。

相关阅读:


事务 = AOP + Connection

Spring 事务这块,很多人背到最后会变成一堆抽象名词:@Transactional、传播行为、隔离级别、TransactionManager。

但如果你从源码主线去看,其实它非常朴素:Spring 声明式事务 = AOP 拦截 + TransactionInterceptor 增强 + PlatformTransactionManager 管连接事务状态。

也就是说,@Transactional 之所以能生效,不是因为 Spring 对方法做了什么魔法,而是因为这个方法最终也被代理了。调用进来后,事务增强逻辑会先运行:

  1. 看当前方法有没有事务属性。
  2. 根据传播行为判断是加入现有事务,还是新开事务。
  3. 通过 PlatformTransactionManager 开启事务。
  4. 执行业务方法。
  5. 根据执行结果决定提交还是回滚。

如果底层是 JDBC / DataSource 体系,那这个事务最终一定绕不开 Connection。Spring 事务本质上还是在管理 Connection 的自动提交、提交、回滚,以及线程绑定。

相关阅读:

而事务传播行为,本质上是为了处理一个更具体的问题:事务嵌套和事务边界冲突怎么协调。

比如:

  • 当前已经有事务了,新方法进来是加入还是新开?
  • 外层方法有事务,内层方法要求强制新事务怎么办?
  • 如果方法要求必须在事务里执行,但当前根本没事务怎么办?

这些问题最终就落到了 REQUIREDREQUIRES_NEWMANDATORYSUPPORTSNESTED 这些传播属性上。

相关阅读:

最后更新于:
⏱️

登录后开始阅读

当前 MVP 版本只开放邮箱密码登录注册。完成登录后即可进入面试题内容。

还没有账号?先去注册