资讯动态

Spring模块结构全景拆解:从Framework到Boot与Cloud的依赖体系

发布时间:2026/10/13 8:21:51 来源:尧图企业网站定制
很多同学用了好几年 Spring写接口、做微服务都很麻利但被问一句“你用的 Spring 到底包含了哪些模块”大概率会愣住。原因也简单日常开发里大家接触最多的是 Spring Boot 的 starter 依赖一个坐标拉进来框架就自己跑起来了很少有人会去关心背后那一堆 jar 是谁、干什么用的。这篇文章专门把 Spring 的模块结构全景拆一遍从 Spring Framework 的内核模块到 Spring Boot 的 starter 组装方式再到 Spring Cloud 的微服务模块家族最后落到实际问题排查和版本搭配。适合想弄懂框架本质、准备面试、或者经常被依赖问题折磨的 Java 开发者这篇读完你至少能清楚自己项目里那些临时的“第三方 jar”分别来自哪个模块。1. 模块结构全景先搞清楚你用的“Spring”到底是哪一层1.1 从“Spring”到“Spring Boot”再到“Spring Cloud”三层认知很多人把 Spring 理解成一个东西实际它是三层结构的生态。最底层是 Spring Framework这才是真正定义 IoC 容器、AOP、事务抽象、MVC 框架这些核心能力的项目。中间层是 Spring Boot它不替代 Framework而是基于 Framework 做自动配置和 starter 封装目标是让你少写配置。最上层是 Spring Cloud一组基于 Spring Boot 的微服务组件集合负责服务发现、配置中心、网关、熔断这些分布式场景下的问题。这个区分很重要因为市面上至少一半的“Spring 依赖冲突”问题都源于没分清自己在用哪一层。比如你项目里 import 了 org.springframework.stereotype.Service这个类来自 spring-context 模块属于 Spring Framework而你配置里写的 spring.datasource.url 前缀处理它的逻辑来自 spring-boot-autoconfigure属于 Spring Boot。如果只对着报错堆栈里的类名去查很容易查错方向。Spring AI、Spring Data、Spring Security 这些项目也是类似的定位它们是 Spring 官方在 Framework/Boot 之上扩展出来的垂直领域模块各有各的 artifact 和命名空间。所以理解模块结构本质上是理解“什么东西是地基、什么东西是装修”。1.2 为什么必须拆模块依赖管理和按需加载的工程考量早期 Java 开发有一种风格一个框架把什么都塞进一个大 jar你哪怕只用到其中一个类也得把几百兆的依赖全部拉进来。随着项目增多类路径冲突、启动变慢、二进制不兼容的问题会一起爆发。Spring 从一开始就选择了模块化路线把不同职责拆成不同的 artifact你用什么就引什么。这样做的好处摆在明面上依赖可控升级某一个领域的模块不影响其他领域按需裁剪一个纯后端服务完全不需要引入 spring-webmvc类路径清晰出问题的时候能更快定位是哪一层出了问题。缺陷也有就是上手门槛变高新手面对一堆 jar 名容易懵。不过别担心Spring Boot 的 starter 已经把“选择恐惧症”的问题解决了大半。理解模块结构并不是要求你手工拼 jar而是让你知道 starter 内部到底替你拼了什么。比如 spring-boot-starter-web 里光是直接依赖就有 spring-web、spring-webmvc、spring-boot-autoconfigure、tomcat-embed-core、jackson-databind 等一堆每一个都有明确的模块定位出了问题也不至于两眼一抹黑。提示现在真的不建议去官网手工下载 Spring jar 包了。官方发布文件都走 Maven 中央仓库或者 Spring 官方仓库用构建工具引入坐标是唯一推荐的方式。手工下 jar 再扔到 WEB-INF/lib那是十年前的做法。2. Spring Framework真正定义“框架内核”的五组模块2.1 Core Containerspring-core、spring-beans、spring-contextCore Container 是整个 Spring 的地基由 spring-core、spring-beans、spring-context 三个 artifact 组成。spring-core 提供最基础的工具类、ASM 字节码处理、资源访问抽象等它不依赖其他 Spring 模块是所有模块的公共底座。spring-beans 负责 BeanDefinition 的解析、BeanFactory 接口的定义、以及 Bean 的实例化逻辑IoC控制反转最核心的“注册加获取”机制就在这一层。spring-context 则是在前两者之上提供 ApplicationContext把事件发布、国际化、资源加载、生命周期管理这些东西补全。很多同学在面试里被问“BeanFactory 和 ApplicationContext 有什么区别”答案其实就在模块分工里。BeanFactory 是 spring-beans 的产物它只负责最基础的 Bean 容器能力ApplicationContext 是 spring-context 的产物它继承了 BeanFactory 的全部能力并扩展了企业级功能。实际项目里你用的几乎都是 ApplicationContext 的实现比如 AnnotationConfigApplicationContext。这个模块组还有一个隐藏知识点spring-context 依赖 spring-aop、spring-expression 等模块。也就是说你只引入 spring-context其实已经间接有了基础 AOP 和 SpEL 表达式的能力。很多开源框架的轻量级集成就是只依赖 spring-context不带 Spring Boot照样能跑起来。2.2 AOP 与 Aspects横切逻辑的模块落点AOP 相关的能力分布在两个 artifact 里。spring-aop 是核心提供 ProxyFactory、Advisor、Pointcut、AspectJExpressionPointcut 这些编程式 AOP 的基础设施以及 Spring 自己的基于 JDK 动态代理和 CGLIB 的代理机制。spring-aspects 则是用来集成 AspectJ 注解生态的模块平时写的 Aspect、Before、Around 这些注解虽然语法来自 AspectJ但解析和执行的框架支撑在 Spring 这一侧。这里有个经常被误解的点Spring AOP 不是完整的 AspectJ它只是借用 AspectJ 的注解语法底层还是 Spring 自己的动态代理。所以切面只能作用在 Spring 容器管理的 Bean 上对容器外的对象无能为力。理解了这一点你就能明白为什么 Transactional 自调用会失效、为什么内部方法调用不会走代理逻辑。从模块结构看AOP 是很多上层能力的基石。事务管理、Spring Security 的方法级权限、异步注解 Async 的开启全部依赖 spring-aop 的代理机制。如果你不需要这些可以不引入 AOP 模块一旦引入就相当于给容器加了一层“代理生成器”Bean 创建流程会变复杂这也是为什么某些场景下 Bean 初始化顺序问题会突然冒出来。2.3 Data AccessJDBC、事务、ORM 的模块家族数据访问相关的模块是实际业务里最常打交道的。spring-jdbc 提供 JdbcTemplate、DataSourceUtils、SimpleJdbcInsert 等把 JDBC 的样板代码消灭掉。spring-tx 是事务抽象模块定义 PlatformTransactionManager、TransactionDefinition、TransactionStatus以及声明式事务注解 Transactional 的处理入口。spring-orm 负责与 Hibernate、JPA 等 ORM 框架做桥接MyBatis 虽然不是 Spring 官方模块但它的 Spring 集成也基于这些基础抽象。平时你在 Spring Boot 里配一个 spring-boot-starter-data-jpa背后自动配置会创建 LocalContainerEntityManagerFactoryBean 并装配一个 JpaTransactionManager这个管理器实现的正是 spring-tx 里的 PlatformTransactionManager 接口。报错里看到的 CannotGetJdbcConnectionException 则来自 spring-jdbc 的异常转换体系Spring 把 JDBC 的 SQLException 转换成了统一的 DataAccessException这也属于模块内建能力。面试里常问的“Spring 事务传播机制”在编程模型中你只需要记住七种传播行为但想深入一点建议直接去看 spring-tx 模块里 AbstractPlatformTransactionManager 的源码。这个类是事务逻辑的核心传播行为、回滚规则、事务同步都在这里实现。至少要知道默认传播行为是 PROPAGATION_REQUIRED默认回滚策略是只对 RuntimeException 和 Error 回滚受检异常默认不回滚。2.4 Web 与 MVCspring-web 与 spring-webmvc 的分工Web 层面的模块经常被混为一谈实际上 spring-web 和 spring-webmvc 是两回事。spring-web 提供 HTTP 客户端 RestTemplate/RestClient、HttpMessageConverter、过滤器链、文件上传解析等通用 Web 能力它本身不需要 Servlet 容器就能存在。spring-webmvc 则在 Servlet 技术栈上实现了 MVC 模式DispatcherServlet、HandlerMapping、Controller、RequestMapping 都来自它。如果项目只是要用 RestClient 调用第三方接口不需要启动一个 Web 应用那引入 spring-web 就够了。如果写的是传统 Spring MVC 接口项目则需要 spring-webmvc。Spring Boot 的 spring-boot-starter-web 一步到位地把这两个都带上同时内置了 Tomcat 和 Jackson所以你会看到引入一个坐标类路径里就多出来几十个 jar。WebSocket 的场景也一样spring-websocket 是单独模块基于它再配合 spring-messaging 的 STOMP 支持能实现服务端主动推送。你在 Spring Boot 里写 WebSocket 配置类的时候发现需要引入 spring-boot-starter-websocket 或者单独加 spring-websocket、spring-messaging 两个坐标原因就在这里——Web 容器帮你启动了但协议处理模块得自己点进来。2.5 Test 与其他支撑模块除了上面几组Spring Framework 还提供 spring-test用来做单元测试和集成测试比如 ContextConfiguration 加载容器、MockMvc 测试 Controller、TestContext 框架管理测试上下文。现代 Spring Boot 项目的 SpringBootTest 本质上也是建立在 spring-test 之上的。还有一些支撑性模块spring-context-support 提供对 Quartz、FreeMarker、邮件发送等第三方库的集成支持spring-expression 提供 SpEL 表达式语言的解析和执行spring-messaging 提供消息抽象spring-jms 提供 JMS 标准消息服务支持。这些模块平时不一定显式关注但会在某些功能点里被间接引入比如 Value 里的 SpEL 表达式能生效就依赖 spring-expression。3. Spring Boot把模块结构重新打包的“组装者”3.1 Starter 机制一个依赖等于一组模块Spring Boot 最大的贡献不是发明了新容器而是创造了一套 starter 依赖规则。每个 starter 是一个空壳工程本身几乎没有任何业务代码它的唯一作用是用 Maven 依赖传递把一组相关模块打包带进来。比如 spring-boot-starter-web 的 pom 文件里声明了对 spring-web、spring-webmvc、spring-boot-starter-json、spring-boot-starter-tomcat 等的依赖拉一个坐标等于拉一整套 Web 开发工具链。starter 命名规则也需要看懂。官方 starter 统一叫 spring-boot-starter-xxx比如 spring-boot-starter-security、spring-boot-starter-actuator。第三方开源组件一般叫 xxx-spring-boot-starter比如 mybatis-spring-boot-starter、spring-boot-starter-data-caffeine 这类自己命名的。看到带 spring-boot-starter 的坐标基本可以确认它是在为 Spring Boot 提供自动配置入口。那 Spring Boot 怎么知道要启用哪些模块呢靠的不是扫描全部类路径那样太慢而是读取 auto-configuration 配置文件。Spring Boot 3.x 里是 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 这个文件里面按顺序列了所有需要尝试加载的自动配置类。每个自动配置类再用 ConditionalOnClass、ConditionalOnMissingBean 等条件判断当前类路径里有没有对应模块有才生效。所以你的 pom 里没引入某模块对应的自动配置就会静默跳过。3.2 自动配置spring-boot-autoconfigure 才是真正干活的模块starter 只负责“带依赖”真正的自动配置逻辑集中在 spring-boot-autoconfigure 模块。这个模块里有数百个 AutoConfiguration 类比如 DataSourceAutoConfiguration、HttpMessageConvertersAutoConfiguration、WebMvcAutoConfiguration。它们做的事大同小异检查条件、装配默认 Bean、绑定配置属性。拿数据源举例类路径里有 HikariCP 和 javax.sql.DataSourceDataSourceAutoConfiguration 就会自动创建一个 HikariDataSource并把 spring.datasource.url、username、password 这些配置通过 DataSourceProperties 绑定上去。如果你手动声明了自己的 DataSource BeanConditionalOnMissingBean 就会让自动配置让步避免两边打架。配置属性绑定也一样spring-boot-autoconfigure 里的 ConfigurationProperties 扫描机制把 application.yml 里的 spring.xxx 前缀映射到 Java 属性类。你写 spring.redis.host 能被 Redis 自动配置识别就是因为 RedisProperties 这个类标着 ConfigurationProperties(prefix spring.redis)。看日志的时候如果发现某个配置项没生效优先怀疑的是匹配的自动配置没被触发而不是配置本身写错了。3.3 常见 starter 内部结构演示为了直观理解“拆一个 starter”是很好的学习方法。下面用表格把几个常见 starter 的核心构成列一下你可以照着自己项目的依赖树验证。Starter 坐标核心传递依赖解决什么问题spring-boot-starter-webspring-web、spring-webmvc、spring-boot-starter-tomcat、jackson-databindWeb 应用、REST 接口spring-boot-starter-data-jpaspring-orm、spring-data-jpa、hibernate-coreJPA 持久化、Repository 数据访问spring-boot-starter-securityspring-security-config、spring-security-web认证授权、安全过滤链spring-boot-starter-actuatormicrometer-core、spring-boot-actuator健康检查、指标监控、应用信息暴露spring-boot-starter-validationhibernate-validator、jakarta.validation-api参数校验、Valid 注解支持看这个表你会发现一个规律几乎所有 starter 都依赖 spring-boot-autoconfigure 和 spring-boot-core。这就是“组装者”的角色定位——starter 不带业务代码只做打包分发。如果你用 IDEA 打开 Maven 工具窗口展开某 starter 的依赖树会看到树枝底下挂着一长串模块名那才是真正的运行时代码。3.4 配置模块从 application.yml 到各个模块的属性绑定Spring Boot 的配置文件模块本质上是一个“转发器”它把 yml 或 properties 里的 key 映射到各个模块的配置类上。这个设计让模块之间的配置风格保持统一又不互相干扰。比如 spring.datasource 前缀只影响数据源模块spring.web 前缀只影响 Web 模块即使写错了前缀最坏的结果也只是配置项没被识别不会把别的模块搞坏。实际项目中常见到 WebSocket 的配置写在 yml 里比如设置端点路径、限制帧大小、配置心跳间隔。但要注意yml 里的 spring.websocket 属性只是把参数绑定到 WebSocketProperties具体生效的服务器端点还是要你自己写配置类注册 Handler 和拦截器。yml 不是万能的它管理的是模块认可的配置项业务层面的代码逻辑还得自己写。聊到新版本Java 21 之后结合 Spring Boot 3.5 可以在 yml 里用 spring.threads.virtual.enabledtrue 打开虚拟线程让 Tomcat 容器用虚拟线程处理请求。这个配置绑定的是 spring-boot 核心模块里新增的虚拟线程相关配置类。版本越新模块里补充的配置项越多因此如果你在看老教程很可能会发现新配置不生效、旧配置被废弃这是模块演进带来的必然结果。4. Spring Cloud 与微服务模块按场景继续拆4.1 Spring Cloud 是模块集合而非单一框架Spring Cloud 不是一个单独下载的框架它是一篮子独立项目的总称所有组件都以 spring-cloud- 前缀的 artifact 独立发布。为什么要这样设计因为微服务的组件选择权应该交给开发者有的系统只需要服务发现有的需要网关有的需要熔断没必要把全部组件绑死在一起。Spring Cloud 做的事情是制定了一套公共抽象比如 DiscoveryClient、LoadBalancerClient各实现模块只要实现这些接口就能接入生态。常见的 Spring Cloud 组件模块有spring-cloud-gateway网关、spring-cloud-openfeign声明式 HTTP 客户端、spring-cloud-config配置中心、spring-cloud-circuitbreaker熔断抽象、spring-cloud-loadbalancer客户端负载均衡。再加上 spring-cloud-commons它提供了服务发现、负载均衡、路由等核心接口真正决定选哪个实现的是你 pom 里引入了哪个具体组件。注意区分spring-cloud-gateway 用的是 WebFlux 技术栈底层是 reactive 的所以它不能和传统 spring-webmvc 共用一个 Web 容器。如果你把网关模块加到一个普通 MVC 项目里启动报错多半是容器冲突。这个问题在 Spring Cloud 早期版本里比现在更明显现在架构上已经分离得很干净但概念上还是要搞清楚。4.2 常用 Spring Cloud 组件模块速查模块坐标核心作用常见替代spring-cloud-starter-gateway基于 WebFlux 的 API 网关、路由转发、过滤器链无明显替代spring-cloud-starter-openfeign声明式 REST 客户端简化服务间调用RestTemplate LoadBalancerspring-cloud-starter-loadbalancer客户端负载均衡集成服务发现Ribbon已进入维护期spring-cloud-starter-config配置中心客户端从 Config Server 拉取外部配置Nacos Configspring-cloud-starter-circuitbreaker熔断降级的统一抽象Sentinel、Hystrix已停更选型建议其实很简单网关用 Spring Cloud Gateway服务间调用如果用 Feign 风格就选 OpenFeign负载均衡默认走 spring-cloud-loadbalancer。不要因为看到“熔断器”就急着引入很多内部系统根本不需要熔断加上反而增加运维负担。4.3 Spring Cloud Alibaba 的模块补充Spring Cloud Alibaba 是阿里开源组件在 Spring Cloud 生态里的适配集合它的模块和官方组件形成互补。最常见的是 spring-cloud-starter-alibaba-nacos-discovery服务发现与配置中心用 Nacos、spring-cloud-starter-alibaba-nacos-configNacos 配置管理、spring-cloud-starter-alibaba-sentinel流量控制与熔断降级、spring-cloud-starter-alibaba-seata分布式事务。Nacos 在国内项目里用得非常多一个重要原因是它同时解决服务注册发现和配置管理两件事部署成本比 Eureka 加 Spring Cloud Config 两套组件更低。Sentinel 则更像一个流量治理组件支持限流、熔断、热点参数防护这些在官方组件里没有对应模块。Seata 是分布式事务方案适合强一致性的跨服务事务场景也属于官方 Spring Cloud 不覆盖的领域。模块结构上理解 Spring Cloud Alibaba关键在于知道它依然是基于 Spring Cloud 抽象实现的Nacos 注册中心实现了 DiscoveryClient 接口Sentinel 适配了 CircuitBreaker 抽象Seata 提供独立的事务协调器。所以你的业务代码不直接依赖具体实现只调抽象接口即可想从 Eureka 切到 Nacos只需要换 starter 改配置业务代码基本不用动。4.4 模块选择建议按场景拆解不要全家桶我见过太多团队一上来就把 Spring Cloud 全家桶坐标全引进去结果服务启动慢、日志刷屏、依赖冲突不断最后排查半天发现很多组件根本没用上。靠谱的做法是按场景清单来只有两三个服务不需要配置中心不需要分布式事务那 Spring Cloud Gateway 加一个服务发现组件就够了。服务间调用频繁可以加 OpenFeign配合 LoadBalancer 做客户端负载均衡。外部依赖多、稳定性要求高再加 Sentinel 或 CircuitBreaker 模块做防护。配置经常变化、服务多到不适合改配置重发才上配置中心。一旦涉及跨库强一致性事务再评估 Seata不要为“分布式事务”这个名词而引入。另外还要考虑团队熟悉程度。引入一个模块意味着团队未来要长期维护它它的升级策略、与 Boot 版本的匹配关系都是成本。Spring Cloud 每套版本和 Boot 版本是严格绑定的升级 Boot 很可能要求跟着升级 Cloud有时候一个组件跟不上就得全部阻塞。5. 模块间的协同机制IoC、三级缓存、Bean 生命周期、事务边界5.1 Bean 生命周期模块如何配合完成一次创建到销毁Bean 的生命周期不是某一个模块单独完成的而是多个模块接力配合。bean 定义解析发生在 spring-beans读取配置类的 Bean、XML 里的 bean 标签、ComponentScan 扫出来的组件全部转成 BeanDefinition。实例化和属性填充也发生在 spring-beans依赖注入在 populateBean 阶段完成。再往后ApplicationContext 和扩展模块开始介入比如 spring-context 的 EventListenerMethodProcessor 会把 EventListener 方法注册成事件监听器spring-aop 的 AbstractAutoProxyCreator 会在初始化后阶段为符合条件的 bean 生成代理。初始化阶段的前后顺序也非常关键BeanNameAware、BeanFactoryAware、PostConstruct、InitializingBean、BeanPostProcessor这些环节面向不同模块写代码时如果不注意顺序就会出现“明明注解了但执行时机跟预期不一样”的问题。销毁阶段同理PreDestroy、DisposableBean、自定义 destroyMethod 的顺序也是固定的。从模块协同角度看Bean 生命周期是理解整个 Spring 源码的最佳入口。这也是“手写 Spring”类教程都喜欢让大家先实现一个简化版容器来模拟这个过程的原因你会发现核心就是扫描定义、创建实例、执行回调、存进单例池这么几步其余全是扩展钩子。5.2 三级缓存原理为什么需要三层 MapSpring 单例 Bean 解决循环依赖靠的是三级缓存这三级缓存在 DefaultSingletonBeanRegistry 的三个 Map 里缓存名称存储内容解决什么问题singletonObjects成品单例 Bean完整初始化后的对象供外部直接获取earlySingletonObjects提前暴露的半成品对象可能被代理过但未完成完整初始化让循环依赖中的另一半拿到引用singletonFactoriesObjectFactory 工厂能在需要时生成早期引用延迟代理生成避免普通 Bean 提前被代理为什么循环依赖需要三层而不是两层核心在于代理和普通对象的区别。如果 Bean A 和 Bean B 互相依赖B 需要拿到 A 的引用时A 还没执行完初始化后处理器。如果直接用 ObjectFactory 生成早期引用还能把 AOP 代理延后到真正需要时再生成。如果 A 有切面B 拿到的引用必须是代理对象而非原始 Bean否则切面逻辑不对。三层设计把“创建半成品”和“生成代理”两个动作解耦了第一次引用时调用 getEarlyBeanReference 生成代理没有循环依赖则不用走这条路径性能影响最小。三级缓存只对单例 scope 的 setter/字段注入循环依赖有效构造器注入和 prototype 作用域的循环依赖直接报错这是 Spring 设计上就放弃的场景。Spring Boot 3.x 里也可以关闭循环依赖支持默认已经不允许了但可以通过配置打开。5.3 AOP 与事务模块边界和原理Transactional 之所以能生效是因为 Spring 事务模块挂在 AOP 机制上面。具体说spring-tx 提供了一个名为 TransactionInterceptor 的增强器它实现了 MethodInterceptor在方法调用前开启事务、执行后提交或回滚。有没有这个增强器取决于你的容器里是否注册了 transactionAdvisor而 Spring Boot 的自动配置会在检测到 spring-tx 和 DataSource/JPA 等模块后自动注册。关键教训事务是在代理层拦截生效的所以同类内部调用不会走代理导致 Transactional 失效。这跟 5.1 里讲的 Bean 生命周期和 5.2 里讲的代理逻辑直接相关。解决办法是注入自身代理、拆分成两个 Bean、或者用 AopContext.currentProxy()但最推荐的还是拆分 Bean让事务方法定义在另一个类里。事务回滚边界同样值得记住默认只有 RuntimeException 和 Error 回滚受检异常不会回滚。想让受检异常也回滚得在注解里写 rollbackFor Exception.class这个写法和 spring-tx 模块的回滚规则实现有关也经常是面试题里考察“为什么我的事务没回滚”的标准答案。5.4 模块缺失的常见报错模式模块结构不清最终都会体现在报错上。最常见的几类ClassNotFoundException / NoClassDefFoundError某个类找不到多半是缺依赖或依赖被排除了。比如启动时看到 org.springframework.web.context.ContextLoaderListener 找不到说明项目引了 spring-webmvc 但没引 spring-web或者传依赖冲突被裁掉了。NoSuchBeanDefinitionException容器里没有某个类型的 Bean两件事需要查模块是否引入自动配置没跑、Bean 是否被扫描到。BeanCurrentlyInCreationException循环依赖而且是 Spring 解决不了的那种比如构造器注入循环依赖或 prototype 循环依赖。NoSuchMethodError类版本不一致最常见是传递依赖里有两个不同版本的 jar拿 V2 编译却加载到了 V1。端口占用或容器启动异常Web 模块的 jar 版本和 Servlet 容器不匹配比如用了 Tomcat 9 却编的是 Servlet 5 API。排查这类问题有一套固定思路先看异常属于哪个包的类再明确这个类对应哪个模块最后打开依赖树看该模块是否存在以及版本是否正确。很多时候问题不是缺模块而是“多模块”把版本搞乱了。6. 实战按需搭建自己的 Spring 模块组合 常见问题速查6.1 最小化模块清单从零拼一个 Spring 项目想验证自己对模块结构的理解建议不要用 Spring Boot 脚手架而是手工建一个最小 Maven 项目只引用最底层的 Spring Framework。下面这个 pom 只引入 spring-context就足够支撑一个 IoC 容器加上事件、国际化等能力。dependencies dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version6.2.0/version /dependency /dependencies注意spring-context 会传递依赖 spring-core、spring-beans、spring-aop、spring-expression所以这个项目已经具备基础 AOP 和 SpEL 能力。之后你再按需添加 spring-jdbc 拿到 JdbcTemplate添加 spring-tx 拿到事务抽象添加 spring-webmvc 拿到 MVC 控制器添加 spring-test 拿到测试上下文。这样每一步都能看见模块变化带来的新能力非常直观。需要提醒的是Spring Framework 版本不是越新越好。Spring Framework 6.x 默认基于 Jakarta EE 9Servlet API 是 jakarta.servlet 前缀如果你还在用老项目的 javax.servlet版本必须回退到 Spring 5.x。这种前缀变化就是模块升级带来的破坏性变更不看模块版本清单直接升级项目必然编译失败。6.2 查看自己项目的模块构成dependency:tree 实用操作想知道“你用的 Spring 到底包含了什么”最直接的方法就是执行依赖树命令mvn dependency:tree -Dincludesorg.springframework用 IDEA 的也可以直接打开 Maven 面板点开某个依赖节点展开传递依赖。执行之后你会看到类似输出[INFO] - org.springframework.boot:spring-boot-starter-web:jar:3.5.0:compile [INFO] | - org.springframework:spring-web:jar:6.2.0:compile [INFO] | - org.springframework:spring-webmvc:jar:6.2.0:compile [INFO] | - org.springframework.boot:spring-boot-autoconfigure:jar:3.5.0:compile [INFO] | - org.apache.tomcat.embed:tomcat-embed-core:jar:10.1.20:compile [INFO] | - com.fasterxml.jackson.core:jackson-databind:jar:2.17.0:compile这份输出直接告诉你一个 starter-web 最终把 Web 技术栈、Servlet 容器、序列化库全部带进来了。多跑几次这个命令你对自己项目里每个模块的来源会越来越有把握。Gradle 项目对应的方式是 dependencies 任务gradle dependencies --configuration compileClasspath6.3 版本兼容矩阵Framework、Boot、JDK 之间的绑定关系Spring Boot 的版本管理会锁定其依赖的所有 Spring Framework 模块版本所以项目里不该手写 Spring Framework 的版本号而是用 Boot 的 BOM 或 parent 管理。下面这张表是实践中最常用的对应关系Spring Boot 版本Spring Framework 版本最低 JDK主要变化Boot 2.7.xFramework 5.3.xJDK 8最后一个 2.x 维护线javax APIBoot 3.0.x-3.1.xFramework 6.0.x-6.1.xJDK 17迁移到 jakarta APIBoot 3.2.x-3.4.xFramework 6.1.x-6.2.xJDK 17支持虚拟线程部分版本支持Boot 3.5.xFramework 6.2.xJDK 17/21完善虚拟线程、模块更精简Spring Cloud 和 Boot 的版本绑定更严格官方发布了一个带版本名的发布线比如 2024.0.0 这个云版本号对应 Boot 3.4。所以升级的时候不能只升 Boot 不管 Cloud也不能只升 Cloud 不管 Boot需要查 Spring Cloud 官方文档里的版本对照表。Spring AI 模块同样对 Boot 版本有要求引入前务必看对应版本的 README。6.4 常见问题速查表模块类报错排查对照报错现象直接原因排查思路推荐解法NoClassDefFoundError: jakarta/servlet/...Servlet API 缺失或版本不匹配检查 spring-boot-starter-web 是否引入、Tomcat 版本加入对应 starter确认 JDK 迁移情况NoSuchMethodError: xxx(参数)类路径里有多个版本的 jarmvn dependency:tree 找重复版本在 pom 里排除旧版本或统一用 BOM 管理NoSuchBeanDefinitionException容器中没有目标 Bean检查扫描路径、自动配置是否生效增加 ComponentScan 覆盖范围、或手动声明 BeanBeanCurrentlyInCreationException构造器循环依赖或 prototype 循环依赖梳理 Bean 引用关系重构依赖避免互相构造注入Failed to introspect Class类缺少运行期依赖看完整异常堆栈找缺哪个类补依赖或调整模块组合Property dataSource is required数据源 Bean 缺失检查 spring-boot-starter-jdbc 是否引入引入对应 starter 或手动配置 DataSource排查模块类问题有一个很实用的节奏先看堆栈第一行定位缺的类再判断这个类应该属于哪个模块然后用依赖树确认该模块的状态。我见过不少人一看到 ClassNotFound 就往 pom 里乱加依赖结果越加越乱。先明确“它在哪个 jar 里”比你猜重要一百倍。最后再分享一个经验新项目启动前先花五分钟把 pom 里的依赖树拉出来看一遍搞清楚哪些模块是自己主动引的、哪些是传递依赖。这个习惯在遇到稀奇古怪的类路径问题时能帮你省下大量排查时间。Spring 的模块结构说复杂也复杂说简单也简单它本质上就是一套按职责切分、按需组合的工程方案。把这套结构装进脑子里你读任何 Spring 相关的代码和配置都会比原来顺畅得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑