ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Maven依赖管理从入门到实战:依赖传递、版本冲突与本地仓库排查指南

Maven依赖管理从入门到实战:依赖传递、版本冲突与本地仓库排查指南 Maven的依赖管理说难不算难但说简单也绝谈不上简单。很多人刚开始接触的时候照着教程在pom.xml里敲一个dependency标签看到GAV坐标填对了、引入不标红就以为完事了。直到项目越做越大IDE突然给你划一条红线或者编译能过、运行跑起来却报NoSuchMethodError又或者打开依赖树看到一串你连名字都没听过的库才意识到这里面的水其实很深。这篇文章就围绕Maven依赖管理里的两块核心内容——依赖配置和依赖传递来展开。为什么单独把这两块拎出来讲因为大部分人在使用Maven时碰到的疑难杂症都出在这两个环节上要么是坐标和scope配错要么是传递依赖里的版本冲突没搞清楚。文章里我会把pom.xml里的每个关键标签拆开讲明白再深入说明依赖传递的规则、冲突仲裁逻辑最后把本地仓库合并、多镜像仓库配置、依赖报错排查这些搜索热度极高的实操问题一起整理出来。适合来看这篇文章的人我大致分成两类。一类是刚装好Maven、正准备给新项目配置依赖的新手你需要先把坐标体系和pom.xml的各个标签吃透才能避免后面掉进散落的坑里。另一类是已经在项目里被依赖冲突折磨到头疼的开发者不管你是用IDEA、Eclipse还是VSCode我都建议认真看一遍第三部分和第四部分里面的排查思路和配置经验可以直接落地使用。整个内容我按照“配置—传递—冲突—仓库—排查”这条主线来组织从底层逻辑讲到实战经验你跟着章节顺序往下读就行。1. 依赖管理的底层逻辑为什么Maven一定要管依赖1.1 没有依赖管理时我们经历过的混乱年代先不要急着写代码我们来回顾一下没有依赖管理工具时Java项目是怎么活下来的。早几年大家都用Eclipse开发Java Web项目jar包要靠自己去官网下载然后手动扔进WEB-INF/lib目录。项目依赖少的时候还好几个jar包放进去就完事。可一旦依赖多起来麻烦就像滚雪球一样爆发哪个jar包是从哪个项目里拷过来的、版本号是多少、有没有被更新过全靠人的记忆力。更揪心的是团队协作。新来的同事要跑项目必须先找老员工要整个lib目录的压缩包然后解压到自己的机器上。如果压缩包里的某个jar版本和原作者的机器不一致项目启动就是一片报错。还有更让人崩溃的情况同一个类的不同版本被同时塞进classpath编译环境选了这个版本运行环境却加载了那个版本最后跑出来的ClassNotFoundException、AbstractMethodError让人完全摸不着头脑。依赖管理要解决的核心问题就是把上面这些“不可控”变成“可控”。说到底就四件事项目依赖了什么东西、从哪里下载、下载哪个版本、这些依赖之间又是什么关系。Maven把这些问题全部标准化了配合中央仓库和本地缓存才让Java项目从“人肉管理jar包”升级为“声明式的依赖管理”。1.2 坐标体系每个依赖都有唯一的身份证在Maven的世界里一个依赖的身份由groupId、artifactId和version三个要素唯一确定也就是大家常说的GAV坐标。这三个概念可以分别类比成公司分组名、项目模块名、版本号。groupId通常是企业域名反写加上项目名比如org.springframework表示Spring官方组织的项目分组com.alibaba是阿里系的开源组件分组。artifactId是具体的库名比如spring-context、fastjson同一个组织下可以有很多不同的artifactId。version就是版本号比如5.3.20、2.2.0。对于开发过程中的快照版本还会带SNAPSHOT后缀比如1.0-SNAPSHOT它表示一个不稳定的随时可能更新的版本。在声明依赖的时候除了GAV坐标还经常会遇到一个type属性。type默认是jar但也可以写成pom、war、maven-plugin等。一个典型的例子是BOMBill of Materials依赖它本质是一个pom文件用来集中维护一堆组件的版本号。引用BOM时如果不加typepom/typeMaven会把它当成jar去解析下载和加载都会出问题。这套坐标体系的意义在哪里在于它给全球所有的Maven构件建立了一个不重复的身份标识。你的项目在pom.xml里写出这段坐标Maven就拿着这个坐标去本地仓库和远程仓库中寻找匹配的文件。整个过程不需要你关心jar文件到底放在哪个目录也不需要你手动下载这正是Maven能取代手动jar包管理的关键。1.3 本地仓库和远程仓库的分工逻辑有了坐标还得有地方存东西。Maven的仓库体系分两层本地仓库和远程仓库。本地仓库默认在用户目录下的.m2/repository所有从远程下载下来的依赖都会缓存在这个目录里。远程仓库则包括Maven Central中央仓库、公司内网的私服以及各种第三方镜像仓库。这里有一个很关键但也经常被忽略的问题Maven解析依赖时并不是每次都跑去远程仓库下载。它的查找顺序是“本地仓库优先远程仓库兜底”。如果你的本地仓库里已经有这个jar的缓存直接复用不再连接远程只有当本地没有时才会去远程仓库下载然后缓存在本地。这个机制平时很好用但也是很多坑的根源。举一个常见的例子你的网络突然断了一下Maven在下载某个jar时中断了本地仓库留下一个不完整的文件。下次构建时Maven以为本地已经有缓存就不再重新下载IDE里一直标红怎么刷新都没用。后面我会详细讲怎么处理这种缓存损坏的问题。这一节你只要记牢一点依赖下载的过程并不总是“从远程拉到本地”先检查本地仓库才符合Maven的真实行为。2. 依赖配置pom.xml的核心标签逐个拆解2.1 依赖声明的基本写法和“三层检查法”夯实基础之后我们来看看pom.xml里最常见的dependencies标签。最基本的写法是这样dependencies dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version5.3.20/version /dependency /dependencies这段配置的含义是当前项目需要spring-context的5.3.20版本默认的作用范围是compile。声明之后Maven不仅会把spring-context本身加载到classpath还会把它自己依赖的其他库一并解析进来这就是后面会展开的依赖传递。实际开发里一个工程pom.xml里配置几十个依赖是常见的事。很多人看到缺什么就往上加什么结果pom里的依赖越堆越多。我自己写依赖有一个习惯叫“三层检查法”第一层先看同一个groupId下有没有已经存在的模块避免重复引入。比如已经有spring-boot-starter-web这种聚合依赖就不要再单独加spring-web和spring-webmvc了否则版本很容易打架。第二层看看这个依赖是不是已经被别的库传递进来了。你可以在IDEA的Maven面板里展开“Dependencies”或者执行mvn dependency:tree查看完整依赖树。如果目标库已经在里面就不必重复声明。第三层确认一定要新增的时候再检查版本号是否存在、scope是否合适。这一层检查往往能帮你避免一大堆低级错误。用这个办法我后来在维护老项目时经常能把pom.xml里的依赖数量砍掉三成以上。依赖少了冲突自然就少构建速度也能快不少。2.2 scope的作用范围与选择依赖配置里scope可能是最容易被忽略、也最容易出错的标签。它决定了这个依赖在哪个阶段有效以及是否会被传递给下游项目。Maven定义了六种scope我用一张表把它们的作用范围整理出来scope编译期测试期运行期是否参与传递典型例子compile是是是是spring-contextprovided是是否否servlet-apiruntime否是是是mysql-connector-javatest否是否否junitsystem是是是否本机指定路径的jarimport仅在dependencyManagement中导入BOM使用---spring-cloud-dependenciescompile最常用代表这个依赖在所有阶段都需要。test只在测试阶段起作用典型的就是junit。provided的意思则是“编译和测试时需要但运行时有容器提供”比如写Servlet项目引入的servlet-api代码编译需要它可最后把war包部署到Tomcat里Tomcat自己就有这套API你再把servlet-api打进去反而容易引起类冲突。runtime的表现和provided正好相反编译时用不到运行时要靠它。最典型的例子是JDBC驱动你的代码里写的是java.sql.DriverManager这些标准JDBC接口编译阶段不需要具体的驱动包但程序运行时必须加载具体实现比如MySQL的驱动类。最后是system。这个scope要配合systemPath使用指定本机上的jar路径。在个人学习环境里偶尔能用在团队项目里就是个雷路径是你机器上的路径同事拉下来根本对不上还会导致整个项目无法构建。我的建议很直接能不用就不用。2.3 版本管理从properties到dependencyManagement依赖数量上来之后版本号的维护就成了一个很现实的问题。如果一个项目里有十几个Spring Boot相关的依赖每个都硬编码版本号等到升级版本的时候你要一个文件一个文件地改漏一个就是你根本不知道的兼容性问题。推荐的第一个做法是在pom.xml的properties节点里统一维护版本号然后在依赖引用处用${xxx.version}来占位properties spring.version5.3.20/spring.version /properties dependencies dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version${spring.version}/version /dependency /dependencies以后想升级就只改一处。不过这里有个隐藏的坑如果子模块里的properties定义了父子同名的属性子模块的properties会覆盖父模块的值导致某些依赖版本突变。排查莫名其妙的版本变化时记得把所有pom里同名属性检查一遍。第二个进阶做法是使用dependencyManagement。它的作用不是直接引入依赖而是把版本号统一“锁”起来。子模块在声明依赖时可以不写versionMaven会去dependencyManagement里找有没有对应的版本声明有就用管理版本没有才要求你显式写。这个机制对多模块项目非常重要。举个例子父pom的dependencyManagement里统一锁定了commons-io的2.11.0子模块只要写groupId和artifactId不用写versionMaven自动选2.11.0。以后升级版本改父pom一处所有子模块全部生效。这不仅能防版本不一致还能避免子模块里写错版本号。2.4 optional与exclusions用途完全不同很多人会把optional和exclusions混为一谈我先说结论optional表示“我这个依赖可以不被下游看见”exclusions表示“我要把某个传递依赖拦在我这个项目之外”两者解决的问题不一样。先看optional。假设你写了一个通用工具库内部为了兼容不同用户的偏好同时依赖了fastjson和jackson两个JSON库。但你心里清楚实际使用这个工具库的项目只会选择其中一种。如果两个依赖默认都被传递出去下游项目就会被迫引入一个根本用不到的JSON库。解决方式是把其中一个依赖标记为optionaltrue/optional这样这个依赖在你自己项目里正常编译、正常打包但不会传递给使用你库的项目。再看exclusions。它通常写在某个依赖的下面用于把这个依赖自身引用的某个子依赖排除掉。比如你引入了一个老旧的库它里面捆绑了某个有安全漏洞的旧版日志框架你没法让它不带但通过exclusions可以把那个日志子依赖从当前项目屏蔽掉。两者用法虽然都有“让我不要某个依赖”的味道但一个管的是“自己不要传出去”一个管的是“外部传进来的我不要”。3. 依赖传递到底是怎样传给下游的3.1 为什么依赖会传递Maven和原先手动管理jar包的最大区别之一就是依赖传递。简单理解就是项目A依赖B而B又依赖C那A在获得B的同时也会自动获得C。让这套机制跑起来的最大功臣就是Spring Boot的starter机制。你在pom里只引入一个spring-boot-starter-web它本身几乎没有代码但它的pom文件声明了一连串的直接依赖比如spring-web、spring-webmvc、jackson-databind、tomcat-embed-core、spring-boot-starter-tomcat等等。这些直接依赖又各自带着自己的依赖反正到最后你一个标签就能把整个Web开发所需的上百个库全部拉到classpath。如果没有依赖传递这些starter设计得再优雅使用上也是灾难。依赖传递的机制里有一个前提条件B对C的依赖声明不能是optional而且C的scope必须是允许传递的类型。如果B把C标记为optional或者B对C的scope是test或provided那么C就不会再继续传下去了。3.2 scope对依赖传递的影响上面说到scope会影响传递那到底哪些传递、哪些不传Maven的规则可以浓缩成下面这张表当前依赖的scope能传递给下游的scopecompilecompile、runtimeprovided无runtimecompile、runtimetest无这张表怎么读以compile为例如果当前项目里某个依赖是compile范围的那么这个依赖自身的compile依赖和runtime依赖都会继续传给下游项目。provided和test不会传递。例如你依赖的一个内部库是provided范围下游项目在使用你这个库时不会自动获得这个provided依赖因为它本身不在传递范围里。这个机制在实际中会带来一些比较隐蔽的影响。假设你在一个common模块中依赖了某个内部的加密库把scope误设成provided你的模块自己编译测试没问题但下游项目引用这个common后却报找不到加密库的类。你一看pom明明写了依赖怎么到别人这就没了其实就是因为provided不参与传递。遇到这种问题第一反应就是去看scope。3.3 冲突仲裁最短路径优先与最先声明优先依赖传递带来的最大副作用就是版本冲突。很多老项目最终都会遇到这样一个经典场景A依赖BB依赖C的1.0版本A同时依赖DD又依赖C的2.0版本。此时C被传递了两次而且版本不一样Maven最后会用哪个Maven遵循的第一条冲突仲裁规则是“最短路径优先”。如果从当前项目到某个依赖的两条路径深度不同深度更浅的那个胜出。还是用刚才的例子A到B到C的路径深度是2A到D到C也是2两者路径长度相等此时进入第二条规则“最先声明优先”。在dependencies节点中谁先声明那它传递过来的版本就生效。这两条规则本身并不复杂但真正折磨人的是它的隐蔽性。举个我实际碰到过的案例项目里用到了某个库的新API这个API在高版本才存在。因为传递依赖的关系最终加载到classpath的是低版本于是编译时IDE甚至都能正确提示跑到运行时才开始报NoSuchMethodError。实际去查才发现是另一个库把低版本给传进来了路径比你的新版本依赖更短仲裁结果就变的很尴尬。遇到这类疑似版本冲突的问题最快的方法是查看依赖树。在命令行执行mvn dependency:tree如果想进一步看到某个依赖的具体引入路径可以加-Dverbose参数它会显示更完整的解析信息。有了依赖树之后你就能判断冲突版本到底是从哪条路径进来的然后再选择是排除、升级还是锁定版本。3.4 拦截传递的三种主要手段当你不想让某个传递依赖进入项目时有三种手段可以用按场景选择了哪种。第一种是exclusions写在有问题的依赖下方专门排除它的某个子依赖dependency groupIdcom.foo/groupId artifactIdfoo-core/artifactId version1.0/version exclusions exclusion groupIdcom.bad/groupId artifactIdbad-lib/artifactId /exclusion /exclusions /dependency典型应用场景是某个库捆绑了一个老旧的日志实现或者捆绑了一个存在安全漏洞的组件你想在不修改源库pom的情况下在自己的项目里把它拦掉。第二种是就近声明。既然最短路径优先那在自己的pom里直接声明一个明确的版本依赖路径深度就是1肯定比任何传递路径都要短。这样做能快速覆盖掉传递版本但它只能影响当前模块多模块项目里其他模块还是会中招。第三种是用dependencyManagement统一锁定版本。在父pom里锁定某个依赖的版本后所有子模块中被传递到的同坐标依赖都会向这个被锁定的版本收敛。它像一把“终极裁决者”把整个项目的版本口径统一起来。我的选型经验是能锁定版本的优先考虑dependencyManagement确实要剔除某个不用依赖的再用exclusions近声明只适合快速临时止血不适合作为长期方案。4. 仓库配置与镜像实战解决下载慢和仓库混乱4.1 两个本地仓库怎么合并这是一个在很多技术社区都热门的话题我有两个Maven的本地仓库repository怎么合并想搞清楚怎么做先得明白本地仓库的构成。本地仓库本质上是一个大目录里面按照groupId/artifactId/version的层级存放着jar文件和pom文件同时还有_maven.repositories标记文件以及.lastUpdated记录文件。既然要合并我们就得有操作路径。我实际处理过类似的问题通常有三种方案效果和成本各不相同。方案一是“指定主仓库法”。比如你有D:/repo1和D:/repo2两个仓库想把repo2的内容并进repo1最简单的方法是选定repo1为主仓库修改settings.xml里的localRepository指向repo1然后通过执行一次完整的项目构建让Maven将repo2中所有独有的依赖重新下载到repo1。这个办法看着是绕了一步但实践下来最省心因为Maven会自行完成所有依赖解析。方案二是“物理合并法”。把repo2目录下的文件按目录结构复制到repo1中。这里一定要小心两种坑一是复制时如果混入了只属于repo2的.lastUpdated文件Maven会认为某些依赖“之前下载失败了”后续构建可能直接跳过导致你的依赖石沉大海二是不建议直接跨仓库覆盖同一个jar的不同版本因为Maven的路径中包含了版本号理论上高版本和低版本文件夹是可以共存的但如果元数据标记文件被错误复制后续解析会混乱。方案三是“多仓库并存法”。如果你并不想真正物理合并只是希望让两个仓库同时生效那正确的做法不是合并而是配置镜像。在settings.xml的mirrors中把另一个仓库路径映射成镜像地址等于是让Maven在找不到依赖时去另一个仓库里寻找。这比物理合并更安全也更灵活。4.2 配置镜像仓库加速国内访问Maven Central经常卡到让人崩溃配置镜像几乎是必做操作。最常用的就是阿里云公共仓库。在settings.xml的mirrors节点中加入mirrors mirror idaliyunmaven/id namealiyun maven/name urlhttps://maven.aliyun.com/repository/public/url mirrorOfcentral/mirrorOf /mirror /mirrors这里的mirrorOfcentral/mirrorOf表示这个镜像只替代中央仓库。很多人图省事写成mirrorOf*/mirrorOf表示所有远程仓库请求都被这个镜像接住。这样设置的好处是统一走镜像、速度快坏处是如果你公司内部还有私服私服上的依赖也会被镜像拦掉导致无法访问。处理办法有两种。第一种是把mirrorOf写得更精确例如mirrorOfcentral/mirrorOf再加上一个私服ID列表实现部分仓库走镜像、私服走原地址。第二种是在项目的pom.xml里显式配置repositories把私服地址直接声明到项目里这样即使全局镜像设置了项目级别的仓库声明也有机会生效。配置多个镜像或者多个仓库还有一个细节Maven会按照声明顺序去尝试直到找到依赖为止。也就是说前面仓库有的依赖后面仓库根本不会被访问前面仓库没有才继续轮到后面。因此仓库排列顺序也会影响构建速度和依赖获取成功率。4.3 在IDEA、VSCode和Eclipse里正确配置Maven很多人的Maven环境变量配好了命令行也能执行但一打开IDE就老是出问题。最常见的坑就是“IDE里用的Maven并不是你安装的那个Maven”。IDEA自带Maven如果你在设置里没有改Maven home path默认会用内置的那个导致你在settings.xml里做的镜像配置、本地仓库配置统统不生效。以IDEA为例正确的做法是打开Settings找到Build Tools → Maven手动把Maven home path改成你安装的Maven目录将User settings file改成你自定义的settings.xml文件路径再把Local repository改成你期望的本地仓库路径。三处都改对之后重启IDE并刷新项目配置才会稳定生效。VSCode则是通过settings.json来配置关键参数包括maven.executable.path、maven.settingsFile、maven.pomfile等。有一点要特别提醒VSCode的Maven插件对settings.xml有缓存机制你修改后如果没立即生效可以执行“Clean Java Language Server Workspace”清理缓存再重新加载。Eclipse里一般是在Window → Preferences → Maven → User Settings里指定配置文件。如果遇到“maven-compiler-plugin无法解析”之类的报错很可能是Eclipse内嵌的Maven版本和你项目要求的不一致换用外部Maven版本就能解决。4.4 用命令行验证配置是否生效改完settings.xml怎么确认配置文件真的被读到了我习惯先在命令行执行mvn help:effective-settings这条命令会展示Maven实际生效的完整设置你可以直接在里面核对本地仓库路径、镜像仓库地址、profile内容等是否都符合预期。然后执行一次完整构建mvn clean install -U这里-U参数是为了强制更新快照和远程依赖避免本地缓存干扰验证结果。如果构建过程中能看到“Downloading from aliyunmaven”这类日志就说明镜像配置已经生效了。5. 依赖问题的排查与修复实录5.1 依赖标红与Unable to resolve dependencyIDE里依赖标红是最常见的现象原因无外乎三类一是本地仓库中对应的jar文件缺失或损坏二是网络连接不到远程仓库三是坐标写错了例如artifactId大小写不一致、版本号不存在。遇到标红我建议按三步走第一步检查坐标对照官方文档核对GAV的大小写和版本号第二步检查settings.xml确认镜像和仓库配置没有拦截正常解析第三步才是清理本地仓库。清理时不要盲目删整个.m2/repository那样会把所有依赖都清掉下一轮构建要全部重新下载特别浪费时间。通常只需要删除指定jar所在目录下的文件或者删除残留的.lastUpdated文件就行。还有一种特殊场景IDE里的错误提示是缓存带来的。IDEA对Maven依赖列表有缓存即使pom已经更新面板里还是旧状态。此时执行一次Maven Reimport或者点击刷新按钮让IDEA重新解析pom而不是靠重启IDE解决问题。5.2 NoSuchMethodError的排查思路如果项目能编译但运行时频繁抛NoSuchMethodError或AbstractMethodError几乎可以断定是版本冲突。你的代码引用了某个库在高版本里才有的方法而classpath上实际加载的却是低版本。排查的完整思路是这样的先执行mvn dependency:tree -Dverbose找到冲突版本对应的引入路径。看依赖树如何发现两个不同版本之间谁属于直接依赖谁属于传递依赖。一般来说直接依赖路径短占优势但如果是传递依赖路径短那间接依赖就会压过你的直接依赖。定位到关键路径后要么在自己的pom里就近声明高版本覆盖掉低版本要么用exclusions把传递进来的低版本拦掉。最稳妥的做法是在父pom的dependencyManagement里统一锁定版本这样所有模块都会收敛。这里补充一句千万不要为了掩盖冲突在命令行里加-DfailNoSuchMethodErrorfalse之类的参数那是在自欺欺人。5.3 本地仓库缓存和lastUpdated文件依赖问题的隐形杀手就是缓存。有时候你明明在pom里改了一个新版本号Maven依然用旧版本这就要考虑是不是缓存问题了。对于快照版本SNAPSHOTMaven默认只在24小时内检查一次更新你刚发布了一个快照本地还是旧的那个执行mvn clean install -U可以强制拉取最新快照。另一个常见情况是jar包下载中断。网络抖动时Maven下载依赖失败本地会留下一个xxx.jar.lastUpdated文件。这个文件的存在会让Maven在下次解析时认为下载已尝试过直接跳过导致你刷新无数次都无法重新下载。遇到这种问题最有效的操作是清理所有.lastUpdated文件find ~/.m2/repository -name *.lastUpdated -type f -exec rm -f {} \;Linux和macOS上都可以用这条命令。Windows用户可以用搜索功能在本地仓库目录下按*.lastUpdated筛选直接删除。清理完再重新构建绝大多数下载问题都能解决。5.4 多模块项目的依赖缺失与幽灵依赖多模块项目里还有一种很难察觉的问题父pom的dependencyManagement里明明锁定了版本子模块里也引用了对应依赖可命令行构建时就是找不到依赖。这种情况十有八九是parent标签写错了子模块没有真正继承到父pom。验证的方法很简单在子模块目录下执行mvn help:effective-pom看看输出的最终pom中是否包含你期望的依赖管理和版本信息。如果parent坐标不对这个命令的输出会暴露出问题。多模块项目里编辑子模块的pom后一定要先在命令行做一次mvn clean install而不是只依赖IDE的刷新功能。IDE有时候不会严格按照Maven的生命周期去解析命令行才是“最终裁判”。还有一种“幽灵依赖”是指你的项目里用到了某个库的类但pom里压根没有显式声明纯粹是另一个库传递进来的。这种现象看起来很爽但实际上非常危险一旦上游去掉那个传递依赖你的项目编译瞬间崩溃。所以我始终建议项目直接使用的类全部显式声明在pom里不要依赖传递关系来“赠送”。6. 一些来自实战的补充经验把前面的内容全部消化掉之后你会发现Maven依赖管理本质上就是一套规则加无数个细节。规则本身不难难的是细节。比如配置镜像时那个mirrorOf*/mirrorOf看似一行配置用不好就拦截掉私服比如provided这个scope用好了能避免重复打包用错了就让下游项目莫名其妙缺依赖再比如本地仓库的合并物理复制文件看着省事结果往往被元数据文件坑到重新下载。我自己这些年最大的体会是依赖出问题别急着删缓存、别急着改版本号先找到证据。这个证据就是mvn dependency:tree的输出它能把所有依赖的来源路径都展示清楚。基于证据做决策而不是基于猜测去试错这才是真正能让Maven为你所用的方式。如果你现在正被某个Maven依赖问题卡住可以在评论区把报错信息和依赖树贴出来大家讨论起来会非常有价值。如果你觉得这篇内容对你有帮助顺手转发给身边同样被依赖折磨的朋友省得他们再走一遍弯路。
返回列表