
1. 为什么用Maven导入Spring告别手工下载jar包的噩梦我还记得第一次接触Spring的时候干的第一件事是打开某个jar包下载站手动找spring-context、spring-beans、spring-core、spring-aop、spring-expression这些包一个个点下载然后挨个拷进项目的WEB-INF/lib目录。运气好的时候一次能配齐运气不好缺个commons-logging运行的时候直接报ClassNotFoundException。那时候最怕的就是这种错误代码里看着一切正常一启动应用就告诉你找不到某个类。后来用了Maven才体会到什么叫导入依赖。所谓maven导入spring框架本质上就是你在pom.xml里声明一句话Maven自动帮你把Spring相关的jar包全部拉下来连带它们依赖的其他组件也一起搞定。这个转变听起来很简单但它彻底改变了Java后端开发的基本工作流。这篇文章就围绕这件事展开Maven怎么把Spring导进来、导入之后怎么验证、遇到报错怎么排查、以及从Spring Framework到Spring Boot整个生态体系里Maven的依赖管理到底应该怎么玩。这篇内容适合两类读者。一类是刚学Java后端、还在用笨办法折腾jar包的新手另一类是已经在用Spring Boot但从不关心依赖是怎么被管理起来的同学。看完你会发现很多让你抓狂的报错根子都在Maven依赖这一层。1.1 Maven在导入这件事上到底做了什么一句话说清楚Maven是一个构建工具加依赖管理工具。它管导入靠的是坐标系统。每个依赖都有一个全球唯一的坐标由groupId、artifactId、version三个部分拼成。举个例子dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version5.3.41/version /dependency这段XML是什么意思就是说我要用Spring的IoC容器模块版本是5.3.41。Maven会去中央仓库或者说你配置的镜像仓库查找这个坐标对应的jar包下载到你的本地仓库默认在C盘用户目录下的.m2/repository然后自动加到项目的classpath里。这个过程最厉害的地方是传递依赖。spring-context本身又依赖spring-core、spring-beans、spring-aop、spring-expression等模块你在pom里只写了spring-contextMaven会把这一整串依赖全部解析出来并下载好。这就省去了手工逐个补齐jar包的操作也是我当年那个ClassNotFoundException噩梦的直接解药。1.2 依赖传递的代价你需要开始理解依赖树凡事都有两面。传递依赖省事但也带来了新的麻烦——你引入了AA又引入了BB又引入了旧版的C而你的项目里另一个D也需要C但要求新版。两个版本同时存在JVM加载类的时候就会出现版本冲突表现往往是NoSuchMethodError、NoClassDefFoundError这类诡异异常。所以用Maven管理Spring不只是会写dependency就行你还得学会看依赖树、做版本控制和排除冲突。这些在后面的章节会展开说我先在这里埋个伏笔你享受依赖传递的便利就必然要承担依赖管理的责任。2. 动手前的版本对齐JDK、Maven、IDEA与Spring的匹配关系导入Spring之前先确认你本地的环境是齐的。很多人在这一步就开始踩坑因为Spring对不同JDK版本是有硬性要求的Maven版本太旧也会出现奇怪的构建错误。2.1 Spring Framework版本和JDK版本的对应关系先说最容易忽略的事Spring Framework 5.3.x系列要求JDK 8及以上而Spring Framework 6.x则要求JDK 17及以上。为什么这里特别强调因为很多人电脑里装的是JDK 8然后去网上复制一段Spring 6的坐标导入后发现编译报错或者启动直接失败。原因很简单Spring 6的字节码基于JDK 17编译JDK 8跑不动。以我常用的组合为例Spring版本最低JDK适用场景Spring 5.3.xJDK 8老项目维护、需要兼容JDK8的环境Spring 6.0.x / 6.1.xJDK 17新项目、Spring Boot 3.x基础Spring Boot 2.7.xJDK 8稳定的生产环境生态成熟Spring Boot 3.2.xJDK 17新技术栈官方长期维护新项目我建议直接用JDK 17加Spring 6这条线因为Spring官方已经明确Spring 5.3系列在2026年结束维护。如果你还停留在JDK 8也不是不能用Spring 5.3.41但要清楚这是一条逐渐过渡的路线。2.2 Maven安装与环境变量配置的细节Maven本身不复杂但安装配置有几个细节值得注意。先去Maven官网下载二进制包解压到某个目录。然后配置环境变量新增MAVEN_HOME指向解压目录在PATH里追加%MAVEN_HOME%\binWindows或$MAVEN_HOME/binmacOS/Linux。完成后在命令行执行mvn -version能正常输出版本信息就说明装好了。这里有一个常见的问题IDEA里明明配置了JDK但Maven使用的还是系统默认的Java。原因是Maven本身是一个Java程序它运行在哪个JDK上取决于环境变量JAVA_HOME。所以你在IDEA里把项目SDK切到17Maven还是可能用8来跑。解决办法是让JAVA_HOME和你的项目JDK版本保持一致或者在IDEA的Maven设置里指定JDK for importer。2.3 配置阿里云镜像仓库第一步就决定了下半场的体验在本地网络环境中直接访问Maven中央仓库下载依赖速度和稳定性都比较感人。所以国内开发者的标准操作是在Maven的conf/settings.xml里配置阿里云镜像mirrors mirror idaliyunmaven/id namealiyun public mirror/name urlhttps://maven.aliyun.com/repository/public/url mirrorOfcentral/mirrorOf /mirror /mirrors这段配置的意思是所有对中央仓库central的请求都走阿里云镜像。这样Spring相关依赖的下载速度会有质的提升。2.4 IDEA里Maven相关配置的三个关键位置在IDEA中进入Settings搜索Maven你会看到三个关键配置项Maven home path选择你本地Maven的解压目录不要用IDEA自带的Maven虽然能用但版本可能和项目要求不匹配User settings file指向你修改过的settings.xml右边有一个Refresh按钮点一下会读取最新配置Local repository显示本地仓库路径确保是你期望的目录很多人配置完Maven后依然找不到依赖多半是因为IDEA还在用默认的全局设置没有切换到你自己那份settings.xml。改完记得右下角点Apply然后在Maven面板点刷新按钮重新导入。3. 核心实操从零到一用pom.xml把Spring Framework请进项目环境就绪下面进入正题。这一节我会完整走一遍用Maven创建Spring工程的流程不涉及Spring Boot先用最原始的Spring Framework让大家看清底层的依赖关系。3.1 创建Maven工程并明确目录结构在IDEA里新建一个普通Maven工程不用选archetype模板直接生成空骨架即可。标准的Maven项目目录结构是src ├── main │ ├── java │ └── resources └── test └── java如果你是拿着已有的Java项目手动加Maven支持只需要在根目录放一个pom.xml并把源码挪到对应目录下。IDEA识别到pom.xml之后会自动把它当Maven工程处理。3.2 第一份Spring依赖spring-context在pom.xml的dependencies标签中添加dependencies dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version5.3.41/version /dependency /dependencies这里只写了一个spring-context。为什么一个就是够因为Maven会解析它的POM文件把依赖链条上的spring-core、spring-beans、spring-aop、spring-expression全部拉进来。你可以在IDEA的Maven面板点击刷新然后展开Dependencies查看会看到这一串相关模块已经自动出现在列表里。这其实就是Maven和Gradle这类依赖管理工具的核心价值你只需要关心直接依赖间接依赖由工具自动处理。手动下载jar包的方案无法优雅地解决这个jar还依赖那个jar的问题而Maven用一份POM文件就把整棵依赖树管理起来了。3.3 写一个Bean验证Spring真的进来了依赖导入是否成功最直接的验证方式是写一个最小的Spring示例跑起来。先定义配置类Configuration ComponentScan(com.example) public class AppConfig { }再写一个简单的Beanpackage com.example; import org.springframework.stereotype.Component; Component public class UserService { public void sayHello() { System.out.println(Spring is running); } }最后写启动入口package com.example; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext ctx new AnnotationConfigApplicationContext(AppConfig.class); UserService userService ctx.getBean(UserService.class); userService.sayHello(); ctx.close(); } }运行main方法控制台输出带Spring标志的日志以及Spring is running就说明整个链路已经通了Maven下载依赖 - classpath集成 - Spring容器扫描Bean - 依赖注入 - 执行方法。很多教程到这就结束了但实际项目远不止一个Bean。我之所以建议先从Framework而不是Spring Boot跑通这一步是因为它能让你看到Spring最原始的运行方式你自己创建容器、自己拿到Bean、自己关容器。理解了这套底层逻辑之后再看Spring Boot里的SpringBootApplication和自动配置就会有原来如此的通透感。4. 从spring-context到全家桶Spring各模块怎么按需选择跑通最简示例之后下一个问题就是项目里到底需要引入哪些Spring模块这取决于你做什么类型的项目。4.1 Spring Framework的模块地图Spring Framework从一个大而全的框架拆成了多个独立模块目的是让项目可以按需引入而不是一次性把整个Spring塞进classpath。常见模块和用途对照坐标artifactId作用spring-core核心工具类被所有模块依赖spring-beansBean的创建、配置、装配spring-contextIoC容器的具体实现ApplicationContext所在spring-aop面向切面编程动态代理支持spring-expressionSpEL表达式语言spring-jdbcJDBC抽象层和DataSource支持spring-tx声明式事务管理spring-webWeb应用基础Multipart等spring-webmvcSpring MVC框架REST接口开发你会发现当你引入spring-context之后spring-core和spring-beans已经作为传递依赖进来了这是正确的。但如果你要写数据库访问需要手动加上spring-jdbc和spring-tx如果你要写Web接口需要加上spring-webmvc。它们不会因为你引入了spring-context就自动出现。4.2 你的项目到底需要哪些依赖按功能类型选择我按项目类型给一个依赖组合参考纯IoC容器项目只需要spring-context用于管理Bean生命周期带数据库访问的项目spring-contextspring-jdbcspring-tx再配合ORM框架如MyBatis注意MyBatis的Spring适配包要跟Spring版本匹配做Web接口的项目spring-webmvcservlet-apijackson-databind做JSON序列化切面功能spring-aopaspectjweaver在pom里写坐标的时候一个高价值的习惯是所有Spring模块统一版本。Spring各模块之间是配套发布的混合使用不同版本极易出问题。比如你引入spring-context 5.3.41又单独引入spring-webmvc 5.2.1轻则警告重则在运行时出现兼容性异常。4.3 一个容易混淆的问题Spring Framework和Spring Boot是两个体系这里是新手最容易懵的点。你搜spring经常看到Spring Boot的教程然后照着配置出来的pom.xml完全不同——因为Spring Boot不是Spring Framework的某个模块而是一个基于Spring Framework之上构建的开发框架。Spring Boot通过starter机制帮你把Spring各模块整合并自动配置了。比如spring-boot-starter-web一个坐标就把spring-webmvc、spring-web、内置Tomcat、Jackson、Spring Boot的自动配置核心全部打包进来了。如果不小心把两个体系混在一起比如在一个Spring Boot项目里又手动加了spring-context还指定了和Boot版本不一致的版本号Maven的仲裁规则可能会让某个旧版本胜出导致Spring Boot的自动配置失效。实际项目里遇到这类问题优先删掉手动添加的Spring Framework坐标让Boot的BOM统一管版本。5. 导入过程中的翻车现场高频报错与排查链路下面这部分是全文含金量最高的地方。我给大量同学解决过Spring导入相关的问题很多报错翻来覆去就那么几个原因。5.1 第一天就遇到的坑依赖下载失败与IDEA红波浪线症状pom.xml里spring-context那行下面标着红波浪线IDEA提示Cannot resolve org.springframework:spring-context:5.3.41。排查链路检查本地Maven仓库有没有这个目录~/.m2/repository/org/springframework/spring-context/5.3.41/。如果只有.lastUpdated后缀文件说明之前下载失败过删除这些.lastUpdated文件因为Maven默认认为最近下载失败的依赖短时间内不会重试检查settings.xml的镜像是否正确把mirrorOf从central改成*看看是不是有部分仓库没走镜像在IDEA右侧Maven面板点击刷新或者命令行执行mvn clean compile看报错信息是网络超时还是HTTP 401。401一般是私有仓库认证配置问题。大多数因为网络原因下载失败的情况配置好阿里云镜像之后删除本地残留的半成品文件再重新刷新就能解决。5.2 运行时ClassNotFound的三种常见原因代码能编译但一运行就报ClassNotFoundException常见有三种情况第一种你手动删除了某个传递依赖。比如项目里有人为了瘦身排除了spring-jcl而Spring的日志组件需要它运行时自然找不到类。第二种scope配置错了。例如把spring-context的scope从默认的compile改成了provided或test运行阶段依赖不在classpath里。第三种多模块工程中子模块引用未声明的传递依赖。父模块里引入了spring-context子模块直接用Spring的注解编译有时能过但运行时报ClassNotFound。这是因为Maven的传递依赖只在同一模块内生效子模块必须显式声明自己需要的依赖。排查时先看IDEA右侧Maven - Dependencies点击某个依赖能看到它的依赖树。如果运行时缺类按上面三个方向逐一排查比较高效。5.3 版本冲突NoSuchMethodError背后是一场Classpath战争开发阶段不报错运行阶段报NoSuchMethodError或NoClassDefFoundError这是典型的依赖版本冲突。场景你在pom里引入了spring-context5.3.41同时另一个库比如某个封装框架传递依赖引入了spring-core5.1.0。在classpath里同时存在两个版本的情况下Maven仲裁规则会优先选择路径深度更短的依赖或者先声明的依赖。最终加载的可能是老版本而老版本缺少新方法于是NoSuchMethodError就来了。解决手段按优先级排列用Spring官方的 统一指定所有Spring模块的版本在冲突的地方用exclusions排除掉旧版本dependency groupIdcom.someframework/groupId artifactIdsome-library/artifactId exclusions exclusion groupIdorg.springframework/groupId artifactIdspring-core/artifactId /exclusion /exclusions /dependency用mvn dependency:tree看冲突的完整路径这个命令是排查依赖问题的第一神器5.4 本地仓库里的jar有问题怎么强制刷新有时候在本地仓库里能看到jar文件但项目依然报错。可能是jar包下载不完整或者被IDE缓存了。强制刷新方式mvn clean install -U-U参数强制Maven更新所有快照版本和远程仓库的信息。如果不行把本地仓库对应目录整个删掉再重新下载。偶尔IDEA的缓存也会导致依赖列表不刷新File - Invalidate Caches并重启就能解决.5.5 两个本地仓库怎么合并一个被问过很多次的场景有人因为换电脑或切换工作环境生成了两个本地仓库目录问怎么合并。我的建议是不要手动合并。Maven的本地仓库本质上是个缓存真正的数据源是远程仓库。把两个仓库的jar文件直接拷贝到同一个localRepository里很容易出现.lastUpdated文件混淆、元数据错乱、同一坐标多版本残留的问题。正确的姿势是修改settings.xml的localRepository指向你想保留的主仓库目录另一个仓库里如果有你自己用mvn install:install-file装进去的私有jar这些在远程仓库找不到的才是需要保留的。把这类jar的坐标记下来然后在新仓库里重新执行install-file装进去其余全部让Maven从远程仓库重新下载一晚上就能拉完这么做看似慢但能避免一大堆隐性问题。6. 进阶从导入依赖到管理依赖——Spring Boot与多模块项目的思路如果你已经能熟练地用Maven导入Spring Framework下一步往哪走我的建议是把注意力从怎么加依赖上升到怎么管理依赖再到怎么把依赖变成项目的一部分。6.1 Spring Boot的starter机制把导包变成选场景Spring Boot引入了一个概念叫starter。它不是一个单独的jar包而是一堆依赖的集合描述符。比如你需要开发Web接口只要引入dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependencyMaven会把这个starter背后关联的一整套依赖都拉进来Spring MVC、内置Tomcat、Jackson、Spring Boot自动配置类等。而且你不需要自己写版本号——因为项目通常会加一个parentparent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.5/version relativePath/ /parent这个parent干的事情之一就是通过dependencyManagement把Spring Boot所有配套依赖的版本统一锁好。这就是为什么说Spring Boot时代你更多时候是选场景而不是配版本。6.2 两种依赖版本统一手段parent继承和import导入spring-boot-starter-parent是通过Maven的继承机制管理版本的。但你的项目可能已经继承了公司内部的父POM没法再加一个parent了这时可以用import方式dependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-dependencies/artifactId version3.2.5/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement区别在于继承只有一个父import可以在dependencyManagement里声明多个BOM。多模块项目的根POM里放一个dependencyManagement子模块只声明坐标不写版本是我现在最推荐的方式。6.3 用命令行快速生成一个带Spring的工程除了在IDEA里手动创建还有一种更省事的方式是直接用Spring官方Initializr的HTTP接口生成项目骨架curl https://start.spring.io/starter.zip \ -d dependenciesweb,mybatis \ -d javaVersion17 \ -o demo.zip得到的是带完整pom.xml的Maven工程直接用IDEA打开就能开始写代码。这种方式特别适合验证Spring Boot新版本的兼容性或快速起一个实验项目。6.4 往深走读源码时别忘了让Maven帮你拉源码包如果你准备看Spring源码比如热词里的三级缓存原理、手写Spring之类的学习路线有个技巧值得记住在IDEA里点击类名进入源码时如果提示Sources not foundMaven会自动下载对应的sources jar。也可以在配置Maven时勾选Download Sources选项一次性拉取所有依赖的源码。我的个人习惯是工作中遇到某个Spring机制不理解就直接用IDEA进入源码追踪调用栈Maven拉取源码这一个动作省去了很多手动找源码的麻烦。比如三级缓存机制里的getSingleton方法从DefaultSingletonBeanRegistry一路往里看是效率比较高的一种读法。6.5 自定义starter当你需要把自己封装的东西也变成一个坐标Maven导入Spring的终极形态是把你封装好的通用能力做成一个自定义starter发布到公司的私有仓库。团队其他人只要在pom里引入这个坐标自动获得你封装好的配置和功能。这个过程本质上是把Spring Boot的starter模式复制一遍一个spring.factories或AutoConfiguration.imports文件加一个自动配置类加上标准Maven坐标。做这个事的意义在于你对依赖的理解从使用工具升到了制造工具。我在实际项目里最深的体会是Maven导入Spring这个动作本身是简单的难的是依赖治理。一个项目写多了之后pom.xml会像滚雪球一样膨胀。定期用mvn dependency:tree检查依赖树、统一版本管理、及时排除无用依赖这些习惯比任何框架知识都更能决定一个项目能走多远。希望这篇内容能帮你把第一步走稳后面的路会顺畅很多。