断网或者内网环境下有些jar包本地明明有Maven却偏要跑一趟maven.aliyun.com然后给你甩一句Could not transfer artifact ... Connection timed out。这问题搞Java后端的基本都遇到过最典型的场景就是你辛苦配好了自定义本地仓库路径甚至把jar包都扔进去了结果一敲mvn clean packageMaven还是去远程拉拉不下来整个构建就挂了。今天这篇文章就把这件事从头到尾掰开揉碎为什么Maven放着本地不用非要去下载自定义本地路径到底该怎么配才能真正生效阿里云镜像那个http://maven.aliyun.com地址为什么会失败以及最终怎么落地解决。无论你是刚学Maven的新手还是被项目依赖折磨的老手这篇都值得花几分钟看完。1. 为什么配了自定义本地路径Maven还是去阿里云下载1.1 Maven解析依赖的底层逻辑一次构建到底去哪里找jar包很多朋友对Maven的认知是它认识pom.xml里的坐标然后自己去找jar包但找jar包的完整路径远没有这么简单。一个Maven项目执行mvn compile或mvn package时解析依赖的过程大致是先读取pom.xml中声明的每个依赖坐标这个坐标由groupId、artifactId、version三元组组成联网世界里拿它定位war包、jar包或pom文件。Maven拿到坐标后会先去本地仓库查找资源。这里说的本地仓库就是settings.xml里localRepository指向的那个目录默认是~/.m2/repository。本地仓库会按照groupId/artifactId/version/artifactId-version.jar的层级结构存放文件。如果在本地仓库找到了对应文件Maven还要做一步检查这个文件是不是刚从远程下载过、有没有带_remote.repositories标记、当前是否需要校验更新。满足条件后Maven直接使用本地文件整个构建过程可以完全离线。如果本地仓库没有这个坐标对应的jar包或pom文件Maven才会根据profile、mirror、repository等配置去访问远程仓库。这就是很多人困惑的根源你配了自定义本地路径但那个路径下的仓库里根本没有pom里声明的那个GAV坐标Maven不可能变出jar包来只能继续往远程仓库走。所以从自定义本地路径读取jar包这件事本质上不是告诉Maven一个本地文件夹它就所有依赖都从里面找而是让Maven在需要依赖时优先且完整地在本地仓库的GAV目录下命中目标。本地没命中它就该去下载这玩意的行为不是你能靠一个localRepository路径就阻止的。1.2 本地仓库、远程仓库、镜像仓库到底是什么关系我在带新人的时候经常发现他们对这三层概念完全是模糊的。这里用比较生活化的方式理一遍。本地仓库就是你自己机器上的一个缓存仓类似你家里囤的米。家里有米就不会下楼买Maven有本地jar包就不会联网下载。远程仓库就是外面的超市Maven可以配置多个比如Maven Central、Apache某个组件的仓库。你项目的pom.xml里repositories标签配的就是想去哪些超市。镜像是啥镜像不是新的仓库而是某个仓库的分店。比如阿里云Maven镜像本质上是把Maven Central等仓库内容同步了一份到自己的服务器上。你配置了镜像后Maven本来要去A超市现在通过mirror规则改道去B分店。对Maven来说它认为自己还在访问A实际上流量已经打到镜像地址上这就是mirror和repository最核心的区别repository是追加一个新仓库源mirror是替换/接管某个仓库的访问路径。本地路径配置了还是去下载一个常见原因是jar包根本不在本地仓库或者版本对不上。另一个常见原因是本地仓库里虽然有那个groupId/artifactId但version目录下的文件损坏、不完整Maven检测到后也会尝试重新下载。还有一种是快照版本比如1.0-SNAPSHOTMaven默认每天会去远程检查一次这个坐标是否有新版本远程连不上就直接报错哪怕本地已经有这份快照也不会直接放行。1.3 本地路径配了却不生效的五个原因结合我这几年看得最多的问题我把localRepository配置不生效归纳成五类大家可以先自我排查。第一配置文件改错了。Maven有两份settings.xml一份在Maven安装目录conf/settings.xml全局配置一份在用户目录~/.m2/settings.xml用户配置。很多教程教的是改安装目录那份但你的IDE或命令行实际加载的可能是另一份或者被IDEA里手动指定的User settings file给覆盖了。第二localRepository路径本身写得不规范。比如路径里有中文、空格或者写到C盘某个没有读写权限的目录。Maven在Windows下访问仓库时一旦遇到权限问题不会直接失败而是在解析时表现得像本地仓库无效从而绕过去走远程。第三jar包虽然放进去了但目录结构不对。你放的是D:/maven_repo/myjar/1.0/myjar.jar但pom里的坐标是com.example:myjar:1.0那Maven要找的是D:/maven_repo/com/example/myjar/1.0/myjar-1.0.jar。不对应他就是找不到。第四IDEA里Local repository选项覆盖了settings.xml。IDEA的Maven设置面板里User settings file下面有一个Local repository输入框IDEA允许你直接填一个路径这个路径的优先级高于settings.xml里的localRepository。所以你改了settings.xmlIDEA却认的是它自己填的那个路径。第五远程仓库策略和_updatePolicy的影响。哪怕本地有release版本如果项目里配置了releases的updatePolicyalways/updatePolicyMaven也会每次强制检查远程。而SNAPSHOT版本更是默认就可能触发远程检查远程不可达时构建直接失败。2. 自定义本地路径的正确配置姿势settings.xml、IDEA与验证2.1 settings.xml到底该改哪个文件先说结论如果你经常用IDEA、也经常用命令行建议统一修改用户级~/.m2/settings.xml。没有就新建一个。Maven加载settings.xml的规则是全局配置 用户配置合并用户配置覆盖全局配置。也就是说~/.m2/settings.xml里的localRepository会覆盖Maven安装目录conf/settings.xml里的值。很多新手直接把apache-maven/conf/settings.xml改了结果IDEA里配置的是另一个Maven发行版或者IDEA用了它自带的Maven那自然不生效。推荐的做法自己新建一个目录比如D:/maven_repoWindows或~/maven_repomacOS/Linux然后把~/.m2/settings.xml里的localRepository改成这个绝对路径。注意不要用相对路径localRepositoryD:/maven_repo/localRepository比localRepositoryD:\maven_repo/localRepository更稳因为XML里反斜杠有转义问题。2.2 localRepository配置的正确写法和路径建议一份最小可用的settings.xml片段长这样settings xmlnshttp://maven.apache.org/SETTINGS/1.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd localRepositoryD:/maven_repo/localRepository /settings路径选择上我提三个建议路径不要有空格和中文Maven在处理带空格的路径时虽然大多数版本能工作但配合IDEA、脚本或CI会产生一些莫名其妙的问题。使用单独盘符或目录不要用系统盘里像C:\Program Files这种权限敏感的位置。如果机器上有多个项目共用注意磁盘空间仓库累积大了非常占空间放在系统盘容易爆。2.3 用命令验证看实际生效的配置到底长什么样配完之后先别急着跑项目。打开终端执行mvn help:effective-settings这个命令会把当前环境下合并后真正生效的settings.xml打印出来注意看输出的localRepository节点是不是你期望的路径。这一步能帮你确认配置文件到底加载对了没有。如果执行报错找不到help插件说明本地仓库里还没有这个插件的jar包而且当前可能处于无法访问远程仓库的状态。这种情况可以忽略换一个方式验证mvn -X compile在debug日志里搜索Local repositoryMaven启动时会把实际生效的本地仓库路径打出来形如[DEBUG] Using local repository at D:\maven_repo看到这行说明路径已经生效。2.4 IDEA里的三处配置少改一处就白搭IDEA里配置Maven的地方在Settings Build, Execution, Deployment Build Tools Maven这里有几个关键的选项必须全部对齐。Maven home path选你实际使用的Maven安装目录。User settings file右边点Override然后选择我们刚修改的~/.m2/settings.xml或者把IDEA指向你自定义的settings.xml文件。Local repository这一栏建议保持为空让它自动读取settings.xml里的localRepository值一旦你手动填了别的路径IDEA就会用这个值覆盖settings里配的路径。改完之后在IDEA右侧Maven面板里点一下刷新按钮让项目重新导入。如果修改过settings.xml但是没刷新IDEA内部的Maven模型还是旧的照样会走老配置。我经常会建议别人顺手把Settings Build Tools Maven Importing里的Automatically download相关选项看清楚有些环境下IDEA会自动勾选Sources和Documentation这也会触发远程下载行为哪怕本地已经有了jar包。3. 让Maven从自定义本地路径读取jar包四种可落地的方案3.1 方案一mvn install:install-file 手动安装jar包到本地仓库最推荐如果你的场景是某个jar包在私服或远程仓库上都没有或者远程仓库就是连不上但我手里有jar文件那么最标准、最不容易出错的方案是用Maven自己的install-file插件把文件安装到本地仓库。命令非常简单mvn install:install-file \ -DfileD:/libs/company-sdk.jar \ -DgroupIdcom.company \ -DartifactIdcompany-sdk \ -Dversion1.0.0 \ -Dpackagingjar执行完Maven会在本地仓库的com/company/company-sdk/1.0.0/目录下生成company-sdk-1.0.0.jar、company-sdk-1.0.0.pom等文件。这样你的项目pom里声明dependency groupIdcom.company/groupId artifactIdcompany-sdk/artifactId version1.0.0/version /dependency构建时Maven就能直接命中本地仓库不再去远程检查。这里有几个细节值得注意。如果jar包本身带一个配套的pom文件可以用-DpomFileD:/libs/company-sdk.pom一并安装不然插件会生成一个非常简化的pom碰到依赖传递时可能丢失依赖关系。如果jar包里没有pom但你又想让它传递依赖其他jar那就只能手动补全pom或者在项目里显式声明它依赖的那些包。如果你想让安装过程更接近真实发布可以加上-DgeneratePomtruemvn org.apache.maven.plugins:maven-install-plugin:3.1.2:install-file \ -DfileD:/libs/company-sdk.jar \ -DgroupIdcom.company \ -DartifactIdcompany-sdk \ -Dversion1.0.0 \ -Dpackagingjar \ -DgeneratePomtrue注意我写的是全限定插件坐标。如果你直接写mvn install:install-fileMaven会自己解析maven-install-plugin的默认版本在无法联网的环境下可能因为下载插件本身失败。用全限定坐标配合本机已有的插件版本可以避免这种二次问题。3.2 方案二手动拷贝jar包到本地仓库目录速度快但要懂结构如果你已经知道本地仓库的目录结构手动拷贝是最快的方式不需要执行任何Maven命令。假设你要装的jar包的GAV是com.example.demo:common-utils:2.0.0那在本地仓库下你需要创建这样的目录结构D:/maven_repo └── com └── example └── demo └── common-utils └── 2.0.0 ├── common-utils-2.0.0.jar └── common-utils-2.0.0.pom然后按照对应文件名放进去。你可以直接从其他机器的仓库里拷一份现成的也可以自己从jar包里生成一份极简pom。pom里只要包含最基本的GAV坐标就行。这里有个非常关键的坑如果你把这台机器的某个jar包拷贝到另一台机器同时把原来机器上跟它同目录的_remote.repositories文件也一起拷过去了Maven很可能认为这个文件是从某个远程仓库来的但现在那个远程仓库的匹配规则变了从而导致解析失败或触发重新下载。我个人的经验是手动拷贝jar包时不要拷贝_remote.repositories和*.lastUpdated文件只拷jar和pom。没有这些标记文件Maven会把它们当作本地原始文件直接使用。如果目录下已经有_remote.repositories直接删除也行Maven下次使用时会重建。这个方法适合临时救急但不适合频繁操作。一旦jar包版本多了手动维护这个GAV目录结构很容易出错。3.3 方案三systemPath scopesystem 直接从任意路径引用理解适用限制有些人想做的从自定义本地路径读取jar包其实是指我不想把jar装进Maven仓库我就放在项目某个lib目录下每次构建直接用。这个需求用systemPath可以实现。dependency groupIdcom.example/groupId artifactIdframework-sdk/artifactId version1.2.0/version scopesystem/scope systemPath${project.basedir}/lib/framework-sdk.jar/systemPath /dependency${project.basedir}是当前项目根目录所以这里引用的是项目根目录下lib目录里的jar。这个方案看起来很方便但我必须提醒几个致命问题。system作用域在Maven打包和依赖传递时默认不带入最终产物。使用spring-boot-maven-plugin打包可执行jar时需要额外配置includeSystemScope否则打出来的jar里不会有这个依赖。项目换电脑或有人直接从SVN/Git拉代码时如果lib目录没同步或者路径变了构建直接挂。这个方案完全绕开了Maven的仓库管理团队协作和持续集成里非常不推荐。所以我的结论是systemPath只适合你自己在自己电脑上临时验证一个jar能不能用别写进正式项目长期维护。想长期用老老实实走方案一或方案四。3.4 方案四搭一个Nexus私服从根上解决下载失败团队场景如果你不是一个人开发而是团队里有一批人都在用同一组内网依赖同时又面临着阿里云镜像偶尔连不上、外网受限等问题那最稳妥的长期方案其实是自己搭一个Nexus仓库。Nexus装好之后你可以把外网依赖在联网时预先代理到Nexus把公司内部jar也上传到Nexus然后所有开发者的settings.xml统一指向Nexus地址。这样即便阿里云镜像完全不可用只要Nexus本地缓存里有对应依赖大家一样可以照常构建。搭建Nexus的步骤这里不展开主要是告诉你Maven依赖管理最可靠的架构是本地仓库 公司内网私服 外网镜像自上而下的链路。本地没有找私服私服没有找外网镜像。这个架构主要解决的是团队一致性减少我本地能跑你本地跑不了的经典问题。单机场景可以先不考虑Nexus但是你要知道有这条解决路径存在。4. maven.aliyun.com路径拉取失败的深度排查与正确配置4.1 先从报错信息判断问题方向当你看到类似这样的报错[ERROR] Failed to execute goal on project my-project: Could not resolve dependencies for project com.example:my-project:jar:1.0.0: The following artifacts could not be resolved: com.example:demo:jar:1.0.0 (absent): Could not transfer artifact com.example:demo:jar:1.0.0 from/to aliyun (http://maven.aliyun.com): Connection refused ...先把报错拆出几个关键信息from/to aliyun指的是你某个mirror或repository的id是aliyun。http://maven.aliyun.com是你配置的地址。Connection refused是TCP层就没连上说明要么阿里云那边拒绝了这个请求要么你们公司网络根本不让你直连这个域名。Connection timed out就是网络超时。PKIX path building failed是证书问题常见于配置了https但本地JVM不信任该证书。不同的报错指向的解决方向完全不同。Connection refused / timed out 优先检查网络和URLPKIX路径构建失败优先换证书或把URL改成http404或no POM优先检查镜像URL路径是否完整以及坐标是否存在。4.2 阿里云镜像地址的正确版本域名后面必须跟路径我看到不少人的settings.xml里写的是mirror idaliyun/id mirrorOf*/mirrorOf urlhttp://maven.aliyun.com/url /mirror这就是一个大坑。maven.aliyun.com这个域名本身不是Maven仓库的端点不能直接用来解析jar包。阿里云Maven仓库的完整地址形如https://maven.aliyun.com/repository/public https://maven.aliyun.com/repository/central https://maven.aliyun.com/repository/google域名后面必须带/repository/xxx这种路径。如果你只配到域名根阿里云服务器会返回一个200页面或者403/404Maven解析不到pom文件表现为拉取依赖失败。另外一个问题是协议。老版本阿里云镜像的HTTP地址是http://maven.aliyun.com/nexus/content/groups/public这个旧地址已经废弃了。现在用http://访问/repository/public大概率会被重定向到https://。Maven在下载依赖时对重定向的处理并不算优雅有时候会报Received fatal alert: protocol_version之类的错误。我个人建议直接全部用https://。所以你要确认自己的URL是这样urlhttps://maven.aliyun.com/repository/public/url如果用的是旧地址urlhttp://maven.aliyun.com/nexus/content/groups/public/url尽快改掉这个地址已经不能用了。4.3 mirror配置方式与注意事项在当前主流的Maven版本下推荐用mirror方式来接管远程仓库访问settings mirrors mirror idaliyun/id namealiyun public mirror/name mirrorOfcentral/mirrorOf urlhttps://maven.aliyun.com/repository/public/url /mirror /mirrors /settingsmirrorOf的值可以很灵活。*表示接管所有远程仓库好处是统一走镜像坏处是如果你还配了公司私有Nexus仓库它也会被强制导向阿里云导致私有依赖找不到。central表示只接管Maven Central实际使用中最常见。external:*表示接管所有非本机的仓库公司本机Nexus不被接管逻辑上更精确。如果你的仓库很多且结构复杂可以用逗号组合比如central,public,!my-private。另外mirror和repository同时存在时mirror规则优先。所以如果你既在pom里配置了中央仓库又在settings里配了mirror实际走的是mirror指向的地址。这本身是特性不是坑但很多人不理解为什么pom写的是repo.maven.apache.org日志里却在访问maven.aliyun.com这就是mirror生效了。4.4 一个隐藏的坑mirrorOf 与私有仓库coexist接着上面说如果你公司内部有一个Nexus私服地址是http://192.168.1.100:8081/repository/maven-public/你希望日常依赖从阿里云拉内部依赖从私服拉。那mirrorOf就不能简单写*不然私服地址会被覆盖成阿里云Maven到了阿里云自然找不到你公司的私有groupId报错就出来了。正确的做法是给每一个远程仓库定义一个仓库id然后在mirrorOf里用排除法mirror idaliyun/id mirrorOf*,!nexus-private/mirrorOf urlhttps://maven.aliyun.com/repository/public/url /mirror这样Maven访问id为nexus-private的仓库时不会走阿里云镜像。这个坑在网络隔离和混合仓库场景下特别常见我见过太多人因为mirrorOf写*查了半天不知道为什么私服的依赖总是cannot find symbol。另外要注意如果项目pom里配置了repositories且仓库地址可以被外网解析但镜像又接管了它有时候你会看到仓库请求成功但坐标404。因为阿里云镜像同步的只是公共仓库内容某些第三方仓库才有的专用插件和依赖镜像上不一定有。这时候要在mirrorOf里排除该仓库id或者干脆不要在pom里配置它改用私服托管。5. 常见问题速查与我的避坑心得5.1 Maven依赖相关常见问题速查表我把日常支持中最高频的Maven依赖问题整理成一个速查表希望能帮你快速定位。问题现象优先排查点解决方向本地仓库有jar但一直去远程下载检查GAV目录是否匹配、检查_remote.repositories、检查SNAPSHOT策略修正目录结构删除标记文件设置updatePolicy为never配置了localRepository但日志用的是别的位置检查用户级/全局settings.xml检查IDEA Local repository面板统一settings.xmlIDEA里保持Local repository为空或指向同一路径阿里云镜像报Connection refused检查URL是否完整、是否该用https使用https://maven.aliyun.com/repository/public阿里云镜像报FKIX path building failedJVM不信任镜像证书导入证书或临时改用http且确认http可用私有依赖在私服里但构建去阿里云找检查mirrorOf是否把私服仓库也接管了mirrorOf改为*,!private-repo-idoffline模式下构建失败本地仓库是否真的包含所有依赖先在线执行mvn dependency:go-offline拉取全量依赖IDEA构建成功但命令行构建失败IDEA可能使用内置Maven或不同settings两边统一Maven版本和settings.xml下载依赖报no POM / 404某个传递依赖坐标在仓库里不存在在本地仓库安装对应pom或排除该传递依赖这张表不能覆盖所有问题但90%的本地明明有jar还是去下载和阿里云下载失败都能在前几行找到答案。5.2 几个我真正踩过的坑写到这里顺便分享几个我实际踩过、也帮别人排过很多次的坑。第一个是关于_remote.repositories文件的。有一回内网开发环境彻底断外网我手工从同事机器上拷了一整个仓库目录结果项目构建还是报一堆依赖找不到。排查到最后发现拷过来的每个jar目录下都带_remote.repositories里面记录的文件来源是central而在我的环境里mirror接管了central指向阿里云阿里云连不上Maven就认为这些文件来源不可信想去重新下载。把仓库里所有_remote.repositories删掉之后构建立刻恢复正常。后来我把这个命令记成了习惯find ~/.m2/repository -name _remote.repositories -delete第二个是SNAPSHOT依赖的更新策略。有一次一个同事的本地仓库里明明有最新的快照jar但每次构建都卡在连接远程仓库的超时上整个团队折磨了一个下午。原因就是我们用的是SNAPSHOT而Maven默认对快照版本会按天检查远程更新。解决办法是在settings.xml里配一个profile把快照更新策略改成never或者在pom里对依赖explicitly设置snapshotsenabledtrue/enabledupdatePolicynever/updatePolicy/snapshots。本地开发阶段快照检查这件事带来的麻烦远大于它带来的价值。第三个是关于IDEA的缓存。IDEA里Maven对settings.xml的读取不是实时的有时候你改了settings.xmlIDEA还拿着旧配置。很多人点了一下刷新按钮发现还是没生效就开始怀疑人生。实际上还要在Build Tools Maven Runner里把Environment variables检查一遍如果配了MAVEN_OPTS或者代理环境变量也会影响连接行为。最省心的排查方式是先用命令行验证命令行OK之后再处理IDEA。最后一个也是被问得最多的为什么我本地明明有某个jar包Maven还是提示缺少依赖其实你把本地仓库翻一遍就会发现依赖坐标可能写的是1.0.0仓库里放的是1.0.0-SNAPSHOT或者groupId的包名大小写有差异。Maven只认完整的GAV三元组不认模糊匹配。这种问题没有捷径老老实实看pom坐标再看仓库目录名逐一核对。回到开头那个问题想让Maven从自定义本地路径读jar包同时不被阿里云镜像的失败拉垮我觉得关键不是去掉远程仓库配置而是把本地仓库变成第一且可靠的数据源。正确的配置、正确的目录结构、正确的依赖坐标这三样对齐之后Maven根本不会想到去连外部网络。至于http://maven.aliyun.com先把URL改成带完整路径的https://maven.aliyun.com/repository/public再保证mirrorOf不要误伤私有仓库这台恼人的下载失败机也就安静了。