news 2026/10/22 19:32:22

Spring模块面试题深度拆解:从模块清单到源码本质

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring模块面试题深度拆解:从模块清单到源码本质

说实话,这道题出现在“每日面试题分享”第141期,我一点不意外。它看起来简单,甚至在不少人眼里就是一道送分题——Spring有哪些模块,背一遍不就行了?但恰恰是这种题,最能看出一个人是真懂 Spring,还是只会背八股文。

先亮观点:这道题表面上考“模块清单”,实际上考三件事。第一,你有没有系统性地啃过 Spring Framework 源码工程,而不只是用了 spring-boot-starter-web 就以为懂了 Spring;第二,你能不能从模块划分中讲清楚 Spring 的设计哲学,比如为什么 core 和 beans 要拆开,为什么 context 要单独存在;第三,你能不能顺着“模块”这个概念,接住面试官下一句追问——哪怕不是三级缓存,也会是 BeanFactory 和 ApplicationContext 的区别,或者是“你说看过源码,那你说说 refresh() 里面干了啥”。

这篇文章我按面试现场的逻辑来写:先拆题目意图,再给你一套可以直接复述的模块全景,然后落到源码工程的模块组织方式,最后给你一套答题节奏和常问追问的应对方案。今天这一篇看完,这道题至少不会再成为你的扣分项。

1. 面试官问这句,到底想听到什么

1.1 一道“送分题”背后藏着三层用意

先想清楚一个事实:Java 后端面试里,Spring 几乎是必问的,而“Spring 有哪些模块”这种题,恰恰是面试官判断候选人基础是否扎实的起点。

第一层用意是探测你的技术广度。如果一个人只答“IoC 和 AOP 两大模块”,或者只答出 spring-core、spring-context、spring-webmvc 这几个经常听的名字,说明他大概率没完整接触过 Spring Framework 的源码工程。面试官心里会立刻给你打上一个标签:这是个“框架使用者”,不是一个“框架阅读者”。

第二层用意是探测你的源码意识。Spring 官方把框架打包为 20 多个独立 jar,每个 jar 对应一个 gradle 子工程。你如果能说出这些子工程的名字,并且顺带提一句“我在源码里看到 spring-beans 的 BeanFactory 接口、spring-context 的 ApplicationContext 接口、spring-core 里的 ResolvableType”,那面试官就知道你是真的 clone 过源码、打开过目录的人。

第三层用意是给后续追问做铺垫。记住,面试官真正想聊的不是这些模块叫什么,而是你对其中某个模块的理解深度。比如你提到 spring-beans,他就可能问“BeanFactory 和 ApplicationContext 是什么关系”;你提到 spring-webmvc,他可能接着问“DispatcherServlet 的处理流程”;你提到 spring-tx,那就等着被问事务失效场景吧。所以这道题的答题策略,一定不是背全模块名就收工,而是“留好钩子,引导面试官问你最熟的方向”。

1.2 这道题最容易踩的三个坑

第一个坑是模块与体系不分。很多人把 Spring Boot 和 Spring Cloud 也算进“Spring 模块”,这不是版本问题,是概念问题。Spring Framework 才是一系列模块的集合;Spring Boot 是在它之上构建的快速开发框架;Spring Cloud 是一套微服务解决方案,基于 Spring Boot,三者的关系不能混淆。面试时如果说“Spring 包括 Spring Boot、Spring Cloud”,那一开口就暴露了。

第二个坑是版本信息停留在老黄历。如果你背书背的是三年前、甚至十年前的模块列表,比如把已经淘汰的 Spring 2.x 时代的 Spring 2.0 模块拿出来讲,就显得很滑稽。当前主流面试场景下,Spring Framework 5.3.x 和 6.x 是重点,这两个版本的模块结构有差异,尤其是 6.x 对 Java 17 的要求、以及若干模块的合并与移除,回答时要注意版本语境。

第三个坑是只报菜名不解释。把 20 个模块名一口气背完,然后闭嘴了。这就像报菜名的相声演员,观众听了个热闹,但不知道每道菜什么味道。面试官没法从你的回答里提取有效信息,他会认为你是临时背了一堆名词。

2. Spring Framework 模块全景:背下来不算完,要理解到源码层

2.1 核心容器四件套,先从最硬的骨头啃起

Spring 的核心容器模块组是整个框架的地基,面试时先说这一组,最能体现你抓住了 Spring 的本质。它们分别是 spring-core、spring-beans、spring-context、spring-expression,再加上一个经常被忽略的 spring-context-support。

先看 spring-core。它是最底层的模块,几乎其他所有模块都依赖它。里面包含了一堆基础工具类和接口,比如资源加载 Resource 体系、类型转换 ConversionService、泛型处理工具 ResolvableType、以及 Spring 自己的日志封装。我在源码阅读初期,最先做的就是翻 spring-core 目录下的 util 包,里面的 StringUtils、ObjectUtils 这些工具类,大家在日常开发中其实天天在用,只是没意识到它们来自这里。面试时你可以提一句“ResolvableType 是用来处理泛型擦除问题的”,这就比你背一百个工具类名都管用。

再看 spring-beans,这是 Spring IoC 容器真正的核心之一。BeanFactory 接口、BeanDefinition 体系、BeanWrapper、属性编辑器这些关键代码都在这里。说句实话,如果你想看“源码”,最值得先读的就是这个模块。BeanDefinition 描述了 Bean 的元信息,BeanFactory 根据它来实例化和装配 Bean。很多人把 BeanFactory 和 ApplicationContext 搞混,实际上 ApplicationContext 在 spring-context 模块里,它是对 BeanFactory 的扩展,增加了事件发布、国际化、资源加载等能力。这两个接口的差别是面试超高频考点,后面我会专门讲。

接着是 spring-context,它承担的是面向开发者的入口角色。我们常用的 ApplicationContext、事件机制 ApplicationEvent、Environment 抽象、MessageSource 国际化支持都在这里。还有 spring-context-support,它做的是第三方库集成的适配,比如 Quartz 调度、邮件服务、Freemarker 模板等。这一组模块说完,你要让面试官感觉到你对“容器”这个概念的层级有清晰认知:core 提供地基,beans 提供 Bean 工厂,context 把工厂升级成完整的应用容器。

最后提一句 spring-expression,也就是 SpEL 表达式语言。它在 Spring MVC 的 @Value 注解、XML 配置的 SpEL 表达式、以及 Spring Security 的权限表达式里都有应用。但说实话,Spring 源码里用 SpEL 的深度并不夸张,面试时简单提一下“这是 Spring 的表达式语言,支持运行时查询操作对象图”就够了。

2.2 AOP、事务与数据访问,面试的高频区

这一组模块面试命中率极高,因为日常开发里我们几乎离不开 AOP 和事务。

spring-aop 是 Spring AOP 的核心模块,里面包含了代理工厂 ProxyFactory、切点 Pointcut、通知 Advice、切面 Advisor 等核心抽象。注意,Spring AOP 本质上是“基于动态代理的 AOP”,默认在 JDK 动态代理和 CGLIB 之间做选择。面试官如果要深入,大概率会问“JDK 动态代理和 CGLIB 的区别”或者“为什么 Spring 6 里默认转向 CGLIB”。这里可以顺带提一句 spring-aspects,它负责集成 AspectJ 注解,比如 @Aspect、@Around 这些注解本身来自 AspectJ 项目,Spring 通过 spring-aspects 模块做桥接。

spring-tx 是事务模块,核心是 PlatformTransactionManager 抽象、事务传播行为、@Transactional 注解支持。这里有个容易混淆的点:事务模块本身不直接操作数据库,它定义的是事务管理的抽象层,具体的实现由各个数据访问技术去对接,比如 spring-jdbc 里的 DataSourceTransactionManager、spring-orm 里的 JpaTransactionManager。面试时如果你能说清楚“事务管理器是抽象,JDBC/JPA/MyBatis 各自提供实现”,就已经领先很多人了。

spring-jdbc 提供了 JdbcTemplate 和 NamedParameterJdbcTemplate,Spring Boot 整合 MyBatis 时底层数据源和事务管理也依赖这个模块。spring-orm 负责对象关系映射集成,比如 Hibernate、JPA。这里需要强调的是,如果候选人平时用 MyBatis 用得很多,面试官一般不会深挖 MyBatis 的源码逻辑,但他会关注你是否理解 MyBatis 与 Spring 的整合原理——而理解整合原理的前提,恰恰是对 spring-jdbc、spring-tx 的数据源管理机制有概念。这也解释了为什么热搜里会出现“spring boot + mybatis 的 java 开源多商户跨境商城源码下载”——很多人想通过完整项目反过来理解 Spring 与 ORM 框架的整合方式。

2.3 Web、消息与测试模块,别在这块丢了分

Web 相关模块是后端开发每天的战场。spring-web 提供基础 Web 能力,包括 MultipartResolver、HttpMessageConverter、RestTemplate 等;spring-webmvc 则是完整的 MVC 框架,DispatcherServlet 就在这里。很多人不知道,spring-webmvc 和 spring-web 是不同 jar,mvc 依赖 web;Spring Boot 里引入 spring-boot-starter-web 时,会自动把 spring-webmvc 和 spring-web 都带进来。

如果你是做响应式编程的,spring-webflux 要单独了解,它基于 Reactor 实现非阻塞 Web 框架。还有 spring-websocket 支持 WebSocket 通信协议。这块面试一般不会往深了问,除非你简历里写了相关项目。但“spring-webmvc 的 DispatcherServlet 处理流程”依然是高频题,建议把它和模块记忆绑定在一起——你说了 spring-webmvc,就要能接住这个流程题。

spring-messaging 是消息抽象层,定义了 Message、MessageChannel、@MessageMapping 等概念,Spring Integration 和 Spring WebSocket 的子协议 STOMP 都基于它。spring-jms 则是基于 JMS 标准的消息集成模块,很多人会把 spring-jms 和 spring-messaging 搞混,一个是面向 Java 消息服务的标准实现,一个是更通用的消息抽象,面试时提一下这个区别就能加分。

最后是容易被忽视的 spring-test。这个模块包含了 TestContext 框架、MockMvc、MockRestServiceServer 等测试利器。我在面试候选人时,如果对方提到“我读过 spring-test 的源码”,我的好感度会立刻上升,因为说明他的源码阅读范围不局限在业务容器,而是覆盖了测试支撑体系。哪怕他只是说“我用过 @SpringBootTest 和 TestRestTemplate”,也比完全不提测试模块强。

3. 源码视角:Spring 为什么要把自己拆成这么多模块

3.1 Gradle 多模块工程结构,先看懂源码目录再说看过源码

“看过源码吗”这句话,很多人理解成“在 IDEA 里点进过某个类看几眼”。但真正看过源码工程的,一定知道 Spring Framework 的 GitHub 仓库是一个 Gradle 多模块工程,根目录一打开就能看到 spring-core、spring-beans、spring-context、spring-web、spring-webmvc、spring-orm、spring-tx、spring-test 这些子目录,和发布的 jar 包一一对应。

我在第一次 clone Spring Framework 源码时,做的第一件事不是找某个类,而是打开根目录的 settings.gradle 文件,看它里面 include 了哪些子工程。这一步能让你瞬间建立“模块即子工程”的直观认知。然后我会按依赖顺序从底层往上层读:先 spring-core,再 spring-beans,再 spring-context。这个顺序不是随便排的,它本身就是模块依赖关系的体现。

在源码工程里,每个子工程下面都有 src/main/java、src/main/resources,重要类的位置和包结构非常清晰。比如你想读容器启动流程,就从 spring-context 下的 AbstractApplicationContext 进入;想读 Bean 定义加载和实例化细节,就在 spring-beans 下找 DefaultListableBeanFactory 和 AbstractAutowireCapableBeanFactory;想读 Bean 实例化完成后的一系列回调,还是在 spring-beans 的初始化逻辑里。这种“以模块为地图、以类为地标”的阅读方式,比到处瞎逛高效得多。

3.2 模块边界就是设计哲学的边界

为什么 Spring 要把功能拆得这么细?这是模块化背后值得理解的本质。面试时如果能讲清楚这一点,会比单纯背模块名高一个层次。

最核心的设计哲学是“可插拔”。如果你只需要一个简单的 IoC 容器,完全可以只依赖 spring-core 和 spring-beans,跑个轻量级的 Bean 管理应用。需要容器高级特性了,再加 spring-context。需要 Web 能力了,再加 spring-web 和 spring-webmvc。每个 jar 依赖关系清晰,不会出现“为了用个 AOP 不得不拖上整个 Web 框架”的情况。

其次是“分层协作”。Spring 明确划分了核心容器、基础设施、数据访问、Web 等层次。这种划分让框架本身的可维护性大大提升。打个比方,它的模块结构就像一个中央厨房:spring-core 是基础食材仓库,spring-beans 是加工线,spring-context 是成品装配车间,spring-webmvc 是专门对外供餐的档口。你如果做的是后台服务,可能只用到加工线;要做 Web 应用,就必须把档口也开起来。这个类比面试时讲出来,既通俗又说明你理解了模块之间“协作但不越界”的关系。

这里还要强调一个边界:Spring Boot 和 Spring Cloud 不算 Spring Framework 的模块。Spring Boot 做的事情是“简化 Spring 应用的初始搭建与开发过程”,它的核心是自动配置和 starter 机制。Spring Cloud 构建在 Spring Boot 之上,解决微服务场景的配置管理、服务发现、负载均衡等分布式问题。你可以在回答结尾处主动划清这个边界,让面试官知道你不光知道“有什么”,还知道“什么不是什么”。

4. 手把手教你组织一波能加分的现场回答

4.1 先归类再展开,1分钟讲清楚 20 个模块

面试现场时间有限,你不能照着模块清单念。推荐的节奏是:先总后分、再抛钩子。

第一步,先给一个“总”:Spring Framework 是模块化的,按职责可以分成核心容器、AOP、数据访问、Web、消息、测试这几大类,大概有 20 多个模块。第二步,按类别快速列举关键模块:核心容器是 spring-core、spring-beans、spring-context、spring-expression;AOP 是 spring-aop、spring-aspects;数据访问是 spring-jdbc、spring-tx、spring-orm;Web 是 spring-web、spring-webmvc、spring-webflux;消息是 spring-jms、spring-messaging;测试是 spring-test。第三步,主动选择一两个你最熟的模块展开,把话题引导到你能深入聊的领域。

我建议每个人都准备一个“最熟模块”话术。比如你研究过容器初始化,就可以说:“我在读源码时重点关注的是 spring-context 里的 AbstractApplicationContext.refresh() 方法,它拆出了十几个步骤,包括 BeanFactory 的准备工作、BeanFactoryPostProcessor 的调用、BeanPostProcessor 的注册、事件多播器的初始化等等。”这几句话说出去,面试官基本会顺着你的引导继续问容器生命周期,而不是突然跳到 AOP。

4.2 用“BeanFactory 到 ApplicationContext”这段源码串起答题节奏

如果现场允许你更深入一点,我强烈建议用“BeanFactory 到 ApplicationContext 的进化”作为主线,把多个模块串成一个有逻辑的故事。

故事的开头是 spring-beans 里的 BeanFactory,它是最底层的容器契约,只管理 Bean 的实例化、依赖注入和生命周期回调。然后是 spring-context 里出生的 ApplicationContext,它在 BeanFactory 之上加了三个关键能力:事件发布(ApplicationEvent)、国际化支持(MessageSource)、以及 Environment 抽象。这三个能力分别对应你在项目里用过的 @EventListener、messages.properties、以及 @PropertySource。

有了这条主线,你不用背模块的孤立定义,而是自然说出各个模块存在的理由:BeanFactory 在 spring-beans 里定义,ApplicationContext 在 spring-context 里定义,context 模块依赖 beans 模块,所以在 gradle 依赖配置里能看到 compile(project(":spring-beans"))。这些话说出来,才叫“看过源码”,而不是“背过名字”。

最后,给一个 30 秒版本的标准回答开头示例:

Spring Framework 本身是一个模块化框架,它的模块按职责可以分成核心容器、AOP、数据访问、Web、消息、测试几大类。核心容器由 spring-core、spring-beans、spring-context 等组成,其中 BeanFactory 的实现在 spring-beans 里,ApplicationContext 在 spring-context 里;Web 模块有 spring-web 和 spring-webmvc;数据访问有 spring-jdbc、spring-tx、spring-orm;另外还有 spring-test 等模块。我最近在读源码时主要看了 spring-context 的容器初始化部分,印象深刻的是 refresh() 方法。

这段话控制在 50 秒内,信息密度高,还留了一个很自然的追问入口。

5. 90% 的人会被追问:那些高频追问和答法

5.1 看过源码与没看过源码的分水岭:容器初始化

答完“有哪些模块”之后,面试官 80% 的可能会顺着说“你说看过源码,那讲讲 AbstractApplicationContext.refresh() 吧”。这个追问躲不开,建议你把它当作必答题来准备。

refresh() 是 Spring 容器启动的入口方法,从 spring-context 模块里的 AbstractApplicationContext 进入。整个方法可以分成三个阶段来理解。第一阶段是准备工作:prepareRefresh() 做环境准备和状态标记,obtainFreshBeanFactory() 刷新并获取新的 BeanFactory。第二阶段是扩展阶段:invokeBeanFactoryPostProcessors() 调用 BeanFactoryPostProcessor 修改 Bean 定义,registerBeanPostProcessors() 注册 Bean 后置处理器,initMessageSource() 初始化国际化资源,initApplicationEventMulticaster() 初始化事件多播器。第三阶段是实例化阶段:finishBeanFactoryInitialization() 这个方法最关键,它会实例化所有非懒加载的单例 Bean,也就是你定义的所有 @Component、@Service 的 Bean 大都在这里被创建出来。

面试时不用背 12 个步骤的细枝末节,但上面这几个关键步骤必须能流畅讲出来。尤其是 finishBeanFactoryInitialization() 之后紧跟着的 finishRefresh(),它会在容器里发布 ContextRefreshedEvent,这就是为什么你在 Spring Boot 里监听 ApplicationReadyEvent 或 ContextRefreshedEvent 可以拿到容器完全就绪的信号。能把“步骤”和“你实际用过的扩展点”关联起来,面试官一眼就能分辨出你是不是真读过。

5.2 三级缓存、AOP 代理与事务失效的速记资产

如果面试官再往下钻,大概率会问循环依赖的处理,也就是大家熟悉的“Spring 三级缓存”。这个话题在热搜里热度一直很高,因为它足够深入,又能体现源码阅读量。

一级缓存叫 singletonObjects,存的是完整创建好的单例 Bean;二级缓存叫 earlySingletonObjects,存的是提前暴露的早期 Bean 引用,可能还没完成属性填充;三级缓存叫 singletonFactories,存的是 ObjectFactory 工厂,用来生产早期引用。这个设计的核心价值在于:容器在 Bean 实例化后、属性填充前,先把一个工厂放入三级缓存,当发现循环依赖时,通过这个工厂拿到早期引用,提前注入给依赖方,从而解开死锁。

这里要说一个经典结论:三级缓存不是用来解决循环依赖本身,而是为了在“需要提前代理”和“不希望提前创建代理”之间取一个平衡。纯解决循环依赖,两级缓存就够了;但 Spring 为了让 AOP 代理尽量在 Bean 初始化完成后才创建,才引入了第三级缓存,用 ObjectFactory 延迟代理。面试官如果对这个话题感兴趣,通常还会追一句“构造器注入的循环依赖为什么解决不了”——答案很简单,因为构造器注入在实例化阶段就必须拿到依赖,此时还没有对象可以提前暴露,三级缓存也无能为力。

另一个高概率追问是事务。你会说 spring-tx 模块,面试官就会顺势问“@Transactional 在什么场景下会失效”。核心要点有这么几个:方法必须是 public,否则 Spring AOP 代理无法拦截;自调用不会经过代理对象,所以同类内部方法调用事务会失效;异常被捕获并被吞掉时,事务不会回滚;数据库引擎不支持事务,那一切白搭;以及传播行为配置不当导致事务没起作用。我建议把这些场景整理成自己的速记清单,面试前过一遍,比临时看源码高效得多。

写在最后的一点体会

这道题我见过太多人答得虎头蛇尾,最大的问题不是不懂装懂,而是“知道大概却组织不出深度”。我个人带新人的经验是:源码不值得逐行读,但核心流程一定要亲手跟过一遍,哪怕只是在 IDEA 里对着 AbstractApplicationContext.refresh() 打断点,一步步看容器从无到有。跟完之后你会发现,Spring 模块那些名字不再是散装的记忆点,而是容器启动过程中不同角色的大本营。

如果现在手边没有源码工程,我建议你今天就花半小时做一件事:clone 一下 spring-framework 仓库,打开 settings.gradle 文件,数一数它 include 了多少个子工程,然后按 spring-core、spring-beans、spring-context 的顺序各打开一个核心类读一读。坚持两周,再看这道面试题,你的回答会完全不一样。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 14:52:13

Agent-Reach 实战:用 CLI 快速搭建与部署 AI Agent

1. 从零认识 Agent-Reach:它到底解决什么问题 Agent-Reach 这个名字,第一次看到的时候我以为是某个网络探测工具,后来翻了一圈资料才搞明白,它其实是一个面向 AI Agent 的 CLI 工具集,核心定位是让开发者用命令行就能快…

作者头像 李华
网站建设 2026/10/16 5:32:30

C++代码冗余消除实战:从识别到重构的完整路线图

C项目的技术债里,最隐蔽、最拖累后期维护的大概就是代码冗余了。不是那种一眼就能看出来的重复函数,而是散落在各个模块里、看起来不太一样、实际上干着同一件事的片段。我在接手老项目和重构遗产代码时,把大量时间花在了跟这类冗余打交道上面…

作者头像 李华
网站建设 2026/10/14 23:22:16

pstack-claude实战:Claude工具链安装配置与进程栈分析

1. 从 "pstack-claude" 这个组合名说起:它到底想解决什么问题第一次看到pstack-claude这个名字,很多人会愣一下——pstack 是什么?claude 又是什么?两者拼在一起是要干嘛?我最初的反应也是这样。先把这两个词…

作者头像 李华
网站建设 2026/10/16 11:05:31

T3 Stack全栈开发实战:tRPC与Prisma打造类型安全应用

第一次看到 t3code 这个名字,我以为是某个代码生成器,后来才反应过来,它其实指向的是 T3 Stack 最佳实践下的那套全栈代码工程。T3 Stack 是 tRPC、Tailwind CSS、TypeScript 的合称,搭上 Next.js 之后,相当于把前端页…

作者头像 李华
网站建设 2026/10/21 21:10:33

Xcelium xrun 仿真回归实战:从编译到多核加速与覆盖率调优

简介:这份资源是面向硬件验证工程师、芯片设计师及半导体设计自动化从业者的 Cadence Xcelium(xrun)操作指南,兼顾初学者与有经验的技术人员。内容从 Linux 环境下的安装检查、单步与三阶段分离仿真讲起,系统梳理基础仿…

作者头像 李华