不管你是刚入行的Java新人,还是被各种诡异依赖问题折磨过的老手,只要你在用Spring写项目,就一定绕不开Maven。Maven和Spring的关系就像汽车和发动机的关系——Spring负责提供各种能力,而Maven负责把这些能力安全、准确地装配到你的项目里。这篇文章我只讲实操,从环境配置到依赖冲突排查,每一块都是真实项目里会踩的坑。
先说清楚一个概念:Maven是构建工具,也是依赖管理工具;Spring是一个庞大的生态。市面上常见的Spring Framework、Spring Boot、Spring Cloud,每个都有几十个模块,模块之间还有版本关联。如果靠手动下载jar包往项目里塞,两周之后你自己都记不住放了多少个包进去。Maven做的就是把这件事标准化:统一目录结构、统一构建流程、统一依赖坐标,让你一句话就能拉齐整个Spring生态的依赖。
这篇文章适合所有使用Maven管理Spring项目的开发者,尤其是刚开始搭建Spring Boot工程、或者已经遇到“依赖包版本冲突”“jar包下载不下来”这类问题的人。我会把你可能遇到的坑都摆出来,一个一个拆给你看。
1. 先搞清楚一件事:Maven到底在解决什么问题
1.1 没有Maven之前,Java项目的依赖管理有多痛苦
我当年刚入行的时候,公司老项目还是用最原始的方式管理依赖:手动下载jar包,扔进WEB-INF/lib目录,然后通过IDE的Build Path把它们加进去。新同事入职光配开发环境就要配三天,因为每个人的IDE版本不同、JDK版本不同、jar包版本也不同。你本地跑得好好的代码,换一台机器就报ClassNotFoundException,查半天发现是某个jar包版本不对。
那时候项目里有一个Excel导出的功能,依赖了poi的3.14版本。后来有人要引入一个报表组件,那个组件内部依赖poi的3.17版本。两个版本同时出现在lib目录下,JVM加载类的时候完全看心情,有时候跑得好好的,有时候突然抛一个NoSuchMethodError。这种问题查起来极度痛苦,因为你没法确定JVM到底加载了哪个版本的类。
这就是Maven出现的根本原因:Java项目需要一个统一的依赖管理机制,把“下载什么包、下载什么版本、传递依赖哪些包、各模块之间怎么组织”这些问题规范化。
1.2 Maven的核心三板斧:坐标、仓库、约定
Maven解决依赖问题的思路非常朴素,就三个概念:
第一是坐标。每个jar包都有一个唯一标识,就像人的身份证号。坐标由groupId、artifactId、version三部分组成。比如Spring Web MVC的坐标是org.springframework:spring-webmvc:6.1.4。有了坐标,Maven就能精确找到对应的jar包,绝不会再出现“这个包到底是哪个版本”的模糊情况。
第二是仓库。Maven把jar包统一放到仓库里管理。本地仓库是你电脑上的一个目录(默认在~/.m2/repository下),下载过的jar包都存在这里,下次再用就直接从本地读取,不需要重复下载。远程仓库是服务器上的jar包集合,最著名的就是Maven中央仓库,Spring官方发布的jar包都会同步到那里。除了中央仓库,很多公司会搭建自己的私有仓库(比如Nexus),用来存放内部公共组件。
第三是约定优于配置。Maven强制规定了项目的目录结构:Java源码放src/main/java,配置文件放src/main/resources,测试代码放src/test/java。这样做的好处是,不管谁接手项目,都能在第一时间找到对应文件,不需要花时间理解项目的自定义结构。
这三板斧组合起来,就让Spring的依赖管理变成了一件极其省心的事:你在pom.xml里声明需要哪些依赖,Maven自动从仓库下载,自动处理传递依赖,自动将jar包引入编译和运行环境。
2. 环境准备:Maven安装配置与国内镜像加速
2.1 下载安装与环境变量配置
配置Maven之前先检查一下JDK版本。这里有一个很容易踩的坑:Spring Boot 3.x要求JDK 17及以上,Spring Boot 2.x可以用JDK 8或11。Maven本身也有版本要求,Maven 3.9+支持JDK 8到JDK 21,建议直接下载最新的Maven 3.9.x版本,兼容性最好。
下载Maven就是去Apache Maven官网找到apache-maven-3.9.x-bin.zip文件。注意要下载bin版本,不要下src版本,src是源码包,普通开发用不到。
下载完成后解压,然后把MAVEN_HOME配置到环境变量里。Windows下在系统变量里新建MAVEN_HOME指向解压目录,然后在Path里加上%MAVEN_HOME%\bin。Mac和Linux用户需要在~/.bashrc或~/.zshrc里加一行:
export MAVEN_HOME=/opt/apache-maven-3.9.9 export PATH=$MAVEN_HOME/bin:$PATH配置完成后,在命令行输入mvn -v,能输出版本信息就说明安装成功了。注意看输出里的Java version,如果显示的不是你期望的JDK版本,多半是JAVA_HOME环境变量没有指向正确的JDK路径,把JAVA_HOME改掉就行。
2.2 settings.xml里必改的三个地方
Maven的全局配置文件叫settings.xml,位置在$MAVEN_HOME/conf下。还有一个用户级别的settings.xml,位置在~/.m2/settings.xml,用户级别的优先级高于全局级别,实际项目里推荐把用户配置文件放~/.m2下,这样即使Maven升级了配置也不会丢。
settings.xml里最重要的三个配置项是localRepository、mirror和profile。
localRepository就是本地仓库路径,默认是~/.m2/repository。这个路径有一个隐患:如果你的系统盘是SSD但空间不大,几百个jar包塞进去会占掉好几个G。我一般会在D盘或数据盘建一个专门的目录,比如D:\maven-repository,然后在settings.xml里改成:
<localRepository>D:\maven-repository</localRepository>mirror就是镜像配置。Maven默认从中央仓库下载文件,但中央仓库的服务器在国外,国内网络环境下下载速度非常慢。我之前有一次构建Spring Boot项目,光下载依赖就花了快半个小时。后来配置了阿里云镜像,同样的项目两分钟就完成了。
profile可以配置JDK版本和激活方式,这个后面会细说。
2.3 阿里云中央仓库镜像配置教程
镜像配置直接写在settings.xml的mirror标签里。在<mirrors>节点下添加:
<mirror> <id>aliyunmaven</id> <mirrorOf>central</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>mirrorOf标成central的意思是,所有从中央仓库发起的下载请求都会走这个镜像。如果公司有私有仓库,也可以把mirrorOf配置成*,!private-repo,意思是除私有仓库外都走镜像。
这里有一个细节需要提醒:不要把所有镜像地址都配成同一个。https://maven.aliyun.com/repository/public其实已经聚合了central和jcenter的内容,日常开发够用了。如果你用的是Spring Boot项目,可能还需要额外配置Spring的里程碑仓库(milestone)和快照仓库(snapshot),在项目的pom.xml里加repositories节点:
<repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>不配置这个的话,如果你用到Spring的某个RC(Release Candidate)版本或者里程碑版本,Maven会直接报Could not find artifact错误。这也是一个很典型的坑:很多人用的是Spring Boot 2.7的RC版本,跑构建就报依赖找不到,其实就是没加这个仓库。
3. pom.xml里Spring依赖包的正确配置方法
3.1 spring-boot-starter-parent:版本管理的定海神针
你新建一个Spring Boot项目的时候,pom.xml里第一行使用的parent一定长这样:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.4</version> <relativePath/> </parent>这个东西极其重要。Spring Boot官方把整个Spring生态的版本都固化在这个parent里了。比如spring-boot-starter-parent:3.2.4对应的Spring Framework版本是6.1.5,对应的Spring Security版本是6.2.3。你只管在你自己的项目里写依赖坐标,不需要写版本号,parent会帮你把所有版本对齐。
这招叫依赖版本集中管理。如果不用parent,你自己手写版本号,很容易写出这种情况:spring-boot-starter-web是2.7.5,spring-security-config是6.1.2,spring-jdbc还是4.3.20——这几个版本根本不兼容,运行的时候各种神奇的错误都会冒出来。
我强烈建议:只要你是Spring Boot项目,一定基于spring-boot-starter-parent构建,不要自创一套不依赖parent的管理方式。自创版本管理不是不行,但那是架构师级别要考虑的事,普通项目用parent就好。
3.2 核心依赖如何正确写入pom.xml
写Spring依赖时,推荐的做法是只用spring-boot-starter-*这种简化坐标。比如要开发Web项目:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>这样一个依赖就会把Spring MVC、内嵌Tomcat、Jackson JSON处理、Spring Core等一堆相关的jar包都带进来。你不需要自己去写spring-webmvc、spring-core这些细粒度的依赖,少了哪一个Maven都会在构建时报错,很让人抓狂。
用到了数据库,那就加上:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>或者如果是MyBatis:
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency>注意,像mybatis-spring-boot-starter这种不是Spring官方出的starter,parent里不会管它的版本,所以必须自己声明version。这是很多新人搞不清楚的地方:凡是org.springframework.boot开头的坐标版本都不写,凡是第三方starter(比如MyBatis、Druid、Lombok)的版本都要自己写清楚。
3.3 scope和依赖传递:什么时候要设置依赖范围
pom.xml里每一项依赖都可以设置scope,它决定依赖的生命周期。用最直白的话说:
compile:默认值,打包的时候要带上,运行的时候也要带上,比如spring-boot-starter-web。provided:编译和测试需要,运行和打包的时候不需要,因为运行环境已经提供了。典型例子是Servlet API,因为Tomcat容器里自带。runtime:编译不需要,运行需要。很少见,JSP引擎类库属于这一类。test:只在测试代码中有效,比如JUnit、Spring Boot Test。system:当你非得引用一个不在Maven仓库里的本地jar包时才用,绝大多数情况下千万别用。
有一种典型错误是把JUnit的scope写成了compile,结果打出来的生产包大几百兆,全是没用的测试库。我见过一个同事把spring-boot-starter-test的scope写成默认,部署到服务器后发现应用内存占用异常大,排查了半天最后发现是测试库一堆包都进去了。所以测试类依赖记得加上:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>关于依赖传递,我讲一个最简单的原则:你声明的依赖会把它自己的依赖也传递到你的项目里。比如你引入spring-boot-starter-web,它内部的spring-webmvc、jackson-databind都会自动出现在你的类路径里。这个机制极大方便了使用,但也诱发了版本冲突。这是下一章的重点。
3.4 dependencyManagement:统一的版本锁
如果你想自己接管版本控制,可以不继承parent,而是在pom.xml里用dependencyManagement统一管理版本。这个机制在multimodule多模块项目中特别常用。
主pom.xml里声明:
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>2023.0.1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>这样各个子模块里使用Spring Cloud的依赖时就不用写版本号了。import这种scope是一个特殊用法,它表示把我声明的这个BOM(Bill of Materials,材料清单)里的依赖管理信息导入进来,相当于一次性获得一整批版本推荐。
最知名的BOM就是spring-cloud-dependencies和spring-boot-dependencies。Spring Cloud每个版本对应一批微服务组件版本,直接引入它就等于你把整个微服务体系的版本都对齐了。若依(RuoYi)框架的后端也是这么做的,把Spring Boot和Spring Cloud的版本都交给parent和dependencyManagement统一管理。
4. 依赖包版本冲突:高频事故现场与排查方法
4.1 冲突到底是怎么发生的
依赖冲突本质上是因为Maven的传递依赖机制太强大了。你的项目引入了A,A引入了B,B引入了C,C里面可能又引入了你自己的某个依赖——多级传递之后,同一个jar包可能同时出现好几个版本。
有一个真实的场景:项目用了Spring Boot 2.7.5,对应的Spring Framework版本是5.3.23。后来接入了一个公司内部的公共组件,这个组件是老项目里抽出来的,它内部依赖了Spring Framework 5.2.15。这时候你的工程里就同时存在5.2.15和5.3.23两个版本的Spring核心类。虽然Maven会做一个“最近优先”的版本选择,但版本冲突的隐患就此埋下了:新版本的类有某个方法,而旧版本的类没有这个方法,运行到那里就报NoSuchMethodError。
这种错误非常阴险,编译期完全没有提示,因为你编译的时候Maven选中了新版本的jar包,方法存在,编译通过。但是运行时因为某种加载顺序问题,JVM可能加载到了旧版本类,运行时直接崩溃。
4.2 如何快速确定是哪个版本在打架
排查依赖冲突不能靠猜,要借助工具观察“依赖树”。
Maven自带的命令是:
mvn dependency:tree这个命令会输出整个项目的依赖树,每一行是一个依赖坐标。配合-Dverbose参数可以看到更详细的信息。如果要专门查某个jar包的来源:
mvn dependency:tree -Dincludes=org.springframework:spring-core-Dincludes后面可以跟groupId或artifactId,Maven会过滤出所有与这个坐标相关的依赖路径。比如输出长这样:
[INFO] com.example:my-project:jar:1.0.0 [INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.7.5:compile [INFO] | \- org.springframework:spring-webmvc:jar:5.3.23:compile [INFO] \- com.company:internal-component:jar:3.2.1:compile [INFO] \- org.springframework:spring-core:jar:5.2.15:compile肉眼看这个树状结构,你就能发现spring-core同时存在5.3.23和5.2.15两个版本。
如果你用IDEA开发,更推荐装一个插件叫Maven Helper。在pom.xml的Dependency Analyzer标签页里,可以可视化地看到哪些依赖存在冲突,哪些版本被Maven解析到了,哪些被剔除了。这个插件还能一键排除冲突依赖,极大提升排查效率。
4.3 解决冲突的三种手段
第一种是排除传递依赖——在<dependency>里加<exclusions>标签:
<dependency> <groupId>com.company</groupId> <artifactId>internal-component</artifactId> <version>3.2.1</version> <exclusions> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> </exclusion> </exclusions> </dependency>意思是:引入internal-component的时候,把它传递过来的spring-core排除掉。这样Maven就会用你项目里已有的、由Spring Boot parent管理的5.3.23版本,冲突自然消失。
第二种是在dependencyManagement里强制指定版本。这种手段适合在公司统一管理依赖的场景。在父pom的dependencyManagement里写:
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.3.23</version> </dependency> </dependencies> </dependencyManagement>这样无论在哪个子模块里,spring-core都统一使用5.3.23。
第三种是修改本项目的依赖声明,让自己声明的坐标与实际的版本保持完全一致。很多时候冲突本身就是因为你自己写错了版本号,比如本来Spring Boot 2.7.5对应Spring Framework 5.3.23,你偏要强写5.2.15,版本当然会对不齐。把所有spring开头的坐标版本号全部删掉,完全交由parent管理,才是根治方案。
这三种手段排优先级的话:能用第三种就先用第三种,不行就用第一种exclusion做局部排除,最后才用dominant全局强制版本。因为强制版本可能引入新的不兼容风险,需要回归测试支持。
4.4 版本冲突的常见连锁反应
依赖冲突的报错不止NoSuchMethodError一种,还有可能是ClassNotFoundException、NoClassDefFoundError。这里科普一下区分方法:ClassNotFoundException是类根本没有出现在类路径上,一般是jar包没有引入,或者被排除了,运行时JVM找不到这个类;NoClassDefFoundError是类按名字存在,但加载的时候失败,更多时候是依赖了同一个类的不同版本,导致某个字段或方法缺失了。看到这两类错误,第一反应就应该是去查依赖树。
除了代码层面的报错,冲突有时还表现为启动时日志里有警告。Spring Boot启动时输出一行“The use of package scanning conflicts with non-explicit class...”或者类似提示,这种信息一般不会让应用起不来,但也说明你的依赖里存在未版本对齐的组件,长期维护会有隐患。比如Spring Security和Spring Framework版本不对齐,启动时就会出现Failed to introspect Class这种警告,极大概率是spring-security的版本比spring-core老,两者不兼容。
5. 那些年踩过的坑:从Maven小白的崩溃瞬间到老手的避坑手册
5.1 构建时下载依赖一直卡住或超时
这个问题八成和网络有关。这里先排除一个最容易犯的错误:每次修改settings.xml之后,IDEA不会自动重载配置,需要重新打开Maven面板点刷新按钮,或者重启IDEA。不然你明明改了镜像地址,构建还是走默认的中央仓库,下载速度当然慢。
如果你已经正确配置了阿里云镜像,发现还是慢,那就看一眼本地仓库是否出现了半截文件。Maven下载中断后,本地仓库会残留一个.lastUpdated结尾的文件,这个文件会导致Maven下次不再重新下载,而是直接判定Could not resolve dependencies。处理方式是直接删掉本地仓库中对应的目录,一般路径是D:\maven-repository\org\springframework下的某个子目录,删完重新构建,Maven会强制重新下载。
5.2 本地仓库的jar包被污染怎么办
还有一种“灵异现象”:某个jar包明明在本地仓库存在,但Maven还是报Missing artifact。这通常是因为本地仓库里的_remote.repositories文件记录了上次下载时的仓库ID,换了镜像地址或换了仓库后,Maven认为这个本地文件与当前仓库不一致,拒绝使用。
解决办法有两个,要么删掉_remote.repositories文件,要么直接删掉对应目录重新下载。这个文件删掉不会影响jar包使用,我实测过多次,删完构建就能过。
5.3 使用IDEA时热部署和依赖不生效
IDEA里经常会遇到一种情况:pom.xml里加了新依赖,代码里也能正常import,但运行时一直提示ClassNotFound。这种问题多了一半原因在于IDEA的缓存。解决方案是Maven面板里的Lifecycle点一下clean,然后执行install,再重启项目。
另外,如果是多模块项目,推荐在IDEA的Maven设置里勾选“Always update snapshots”。Spring、若依框架这类经常更新快照版本的工程,如果不开启这个选项,本地引用到的快照版本永远不会更新,改代码后看不到效果,排查半天发现不是自己代码的问题,而是依赖没更新。
5.4 install和package的区别,别再搞混了
命令行构建Spring项目用的最多的两个命令是mvn clean install和mvn clean package。很多人以为它们效果一样,其实差很远。package只将当前项目的产物打包成jar或war,而install除了打包,还会把当前项目的jar安装到本地仓库。如果你有多个模块,模块A依赖模块B,模块B修改了代码,必须执行mvn install把新版本的B装入本地仓库,否则A的运行环境里还是旧版本的B。
我第一次接触若依框架的时候就是这个坑,改了后台的system模块,前端admin模块怎么都不生效,后来才明白必须先install核心模块。这个经验后来在做任何多模块项目时都适用,记住一条原则:**本地依赖的模块修改后,必须先用mvn install。
5.5 各种Spring模块常见版本匹配参考
说了这么多,直接给出一张常见的Spring生态版本对应参考表,方便你排查版本时对照。
| 场景 | Spring Boot版本 | Spring Framework版本 | Spring Cloud版本 | 推荐JDK |
|---|---|---|---|---|
| 老项目维护 | 2.5.x | 5.3.x | 2020.0.5 / 2021.0.x | 8 / 11 |
| 常规新项目 | 2.7.x | 5.3.x | 2021.0.x | 8 / 11 / 17 |
| 新项目推荐 | 3.2.x | 6.1.x | 2023.0.x | 17 / 21 |
| 微服务新项目 | 3.1.x | 6.0.x | 2022.0.x | 17 / 21 |
这组数据不需要死记,但你心里要有概念。比如Spring Boot 2.7对应的Spring Cloud是2021.0.x(也叫Jubilee),如果自己把Spring Cloud版本改成2022.0.x,大概率会因为依赖的Spring Framework版本冲突而启动失败。遇到这种情况,全部改回来,也不要相信网上说“可以混用”的帖子,自己动手实验过的人才知道混用会搞出多少幺蛾子。
5.6 快照版本和正式版本的使用原则
Maven里版本号带-SNAPSHOT后缀表示快照版本,比如spring-cloud-starter-alibaba-nacos-discovery:2023.0.0-SNAPSHOT表示还处于开发中的版本。这类版本发布不稳定,同一个版本号每天可能会更新好几次。生产环境的项目一律不要使用SNAPSHOT版本,因为今天构建成功,明天再构建可能就是完全不同的代码行为。如果非要用,那就锁死仓库里的一个时间点,或者干脆用release版本。
在Maven配置里,为了加速开发,你可以在settings.xml的profilename开快照更新:
<profiles> <profile> <id>allow-snapshots</id> <activation> <activeByDefault>true</activeByDefault> </activation> <repositories> <repository> <id>snapshots</id> <url>https://maven.aliyun.com/repository/public</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> </profile> </profiles>这里注意一个问题:如果同时配置了mirrorAliyun镜像用于中央仓库,再把阿里云镜像地址又加到仓库里,可能会有冲突。多个repository配置尽量贴合实际需求,不要无脑堆。
6. 从Maven视角看Spring项目:几个必须理解的工作原理
6.1 Spring的三级缓存和控制反转,和Maven有什么关系
很多人学Spring的时候会在“三级缓存”“控制反转”这些概念上卡住。这里我讲一个纯经验层面的联想:控制反转(IoC)的本质是对象之间的依赖由容器管理,而Maven的本质是jar包之间的依赖由工具管理。两者是同一个思想在不同层面的体现:不让你手工维护复杂依赖关系,而是通过一套中央机制自动搞定。
Spring在实例化bean时为了避免循环依赖,使用了三级缓存:一级缓存存的是完全初始化好的单例对象,二级缓存存的是提前暴露的早期对象,三级缓存存的是对象工厂。如果你之前靠手动new对象拼装复杂系统,一旦出现循环引用(A引用了B,B引用了A),程序直接栈溢出崩溃。但Spring通过三级缓存,把创建过程分阶段进行,从而支持了循环依赖。
这和Maven有什么关系?其实关系在于理解“依赖图”的概念。Maven构建项目的时候会根据pom.xml生成一张完整的依赖图,Spring创建bean的时候也会根据各类注解和配置生成一张对象依赖图。你使用Maven管理Spring依赖时,如果理解了pom里的依赖树是怎么组织的,反过来你也能更快地理解Spring bean之间是怎么组织依赖关系的。很多人从Maven控制依赖版本过渡到理解Spring三级缓存,只差一个“依赖树思维”的转换。
6.2 Spring Boot自动配置和Starter依赖的配合
Spring Boot之所以能实现开箱即用,离不开spring-boot-starter-*依赖和自动配置机制的配合。你引入一个starter依赖,Maven把它包含的jar包拉进类路径;Spring Boot在启动时会扫描类路径下的META-INF/spring.factories文件,加载对应的AutoConfiguration类,并根据条件注解(@ConditionalOnClass、@ConditionalOnMissingBean等)决定是否启用某些配置。
这个机制带来的一个实际影响是:你删掉或换掉某个依赖时,Spring Boot的自动配置行为也会改变。比如spring-boot-starter-web被排除后,Spring MVC的自动配置就不再生效,项目里的Controller接口全部无法访问。所以修改依赖时要考虑自动配置的影响面,不要觉得删掉一个无关紧要的starter不会出事——它背后可能关联了一大片自动配置。
6.3 手写一个“简化版Spring”能帮你更好理解Maven配置
网络上很多人推荐通过手写一个简化版Spring容器来学习Spring原理,我觉得非常值得试。这里的精华在于:你不会一开始就遇到复杂的自动配置,而是从零开始用Java反射、注解、类扫描去实现对象注册和依赖注入。当你声明的@Component注解能被自己写的扫描器识别时,你才能真正理解spring-context这个jar包到底在classpath里做了什么。
手写的建议路径是:先实现一个IoC容器,管理bean的注册与获取,再实现@Autowired的自动注入,最后再去研究Bean生命周期的各个阶段。等你能徒手跑通这一套,回过来看Maven的dependency:tree,你会发现从前觉得乱成一团的jar包关系变得清晰了:哪个模块管理了哪些Bean、依赖了哪些外部库、加载顺序如何,全都能对得上号。
7. 实操心得:如果这个项目重做一遍,我会怎么配置Maven
最后分享一点我个人实际使用Maven管理Spring项目的体会。如果你是第一次搭建一个新项目,不要一上来就各种拷贝网上五花八门的pom配置,我的建议是:
第一步,直接从spring-boot-starter-parent作为parent,把Spring Boot版本确定为当前稳定版,比如3.2.4或2.7.18。第二步,按需引入starter依赖,Web、测试、数据库、Redis、消息队列缺什么加什么。第三步,配置好settings.xml的阿里云镜像和本地仓库路径,确保团队每个人环境一致。第四步,写好README里的Maven构建说明,明确团队统一用mvn clean install构建,统一JDK版本。
这样的方案也许看起来普通,但它是最稳的。普通项目追求的不是炫技,而是团队成员之间不互相踩坑。Maven这个工具的哲学本来就是“约定优于配置”,你强行加入各种自定义插件、自定义依赖过滤、自定义仓库策略,短期内可能解决了某个具体问题,长期看增加了所有人的维护成本。
最后再分享一个实用小技巧:每次大版本升级Spring Boot时,先在mvn dependency:tree里对比升级前后的依赖树变化,重点关注哪些jar包的版本被改了,哪些子依赖被升级了。我前年从Spring Boot 2.6升到2.7时,就是这样先找出所有版本变化的组件,再重点测试这些组件对应的功能,最终整个升级过程只花了半天时间,上线后也没有出现任何异常。这个习惯我一直保留,也希望你们用得上。