maven如何忽略指定的远程仓库
问题背景
最近在进行云平台提供商迁移的过程中遇到一个跟maven仓库相关的有趣的问题,跟大家分享一下。
由于公司nexus私服迁移到新机器上,启用了新的地址,原来的地址下线,在Jenkins打包机器上重新配置好 settings.xml文件后,发现还是有部分应用在拉取依赖包时仍然访问老的nexus私服地址,导致卡住进而超时,如下图所示:

经过排查,发现这是因为不少业务方在上传jar包到私服时指定了仓库地址(老的地址),而很多jar包配置都是互相copy的,导致这样的包含老地址的jar包非常多,如下所示:

maven远程仓库的顺序
遇到这个问题的时候其实有点诧异,因为按照,应该是全局settings.xml或者 .m2/settings.xml中配置的仓库地址优先,不应该出现类似问题才对。
可真实情况是,在下载依赖包时,依赖包中定义的仓库地址也会生效,并且会和settings.xml中定义的仓库地址、当前构建项目中指定的仓库地址组成一个候选仓库地址列表,同时发起下载请求,并且会尝试更新一些SNAPSHOT包的metadata信息。
举个例子,假设当前构建项目为A,A中定义了仓库地址repoA,依赖b.jar,b.jar的pom.xml中自定义了仓库地址repoB,此时settings.xml定义仓库地址为repo,那么在下载b.jar以及b.jar传递依赖的其他依赖包时,都会同时使用到 repoA, repoB及repo,如下图所示:



那么面对这种旧仓库地址已经下线,而很多jar包仍然依赖着老地址的尴尬局面,该如何解决呢?总不能让业务方都重新发布一遍jar包,替换旧仓库地址吧,这样做法工作量巨大,而且很多jar包都没人维护了,所以必须寻找其他解决方案。
解决方案
从前面的分析得知,要想解决上述问题,必须在拉取依赖包时能屏蔽某些不想使用的仓库地址。为此,我们总结了三种方案,供大家参考。
方案一 设定连接超时时间
我们知道maven底层是通过http client来拉取数据的,一定是有连接建立超时时间或者数据读取时间限制,那么对于想要屏蔽的地址,只要我们将该时间设置的尽可能短,那么就可以达到忽略某些仓库地址的目的。
比如,对于已经下线的maven-yuceyi 和 clubfactory 仓库,我们在settings.xml中进行如下设置():
clubfactory
admin
xxx
1000
5000
maven-yuceyi
admin
xxx
1000
5000
那么对于已经下线的旧仓库地址来说,在1000ms内就会连接失败,可以解决一直卡住的问题,如下图所示:


这里已经可以解决旧仓库地址无法连接导致卡住的问题,但是不太优雅,因为构建日志中会出现很多异常日志。那么有没有既可以屏蔽旧仓库地址,又不引入异常日志的方案呢?答案是肯定的,这就是接下来介绍的方案二。
方案二 使用mirrorOf机制
我们知道,在maven的settings.xml配置中,有一个mirror配置,用来把某些仓库的请求都由指定的仓库地址进行代理()。那么对于已经下线的旧仓库地址来说,只要我们把它们配置为使用新仓库地址进行mirror,就实现了屏蔽某些仓库的目的,并且不会出现异常,因为不会向旧地址发起请求。
mirror配置如下:
maven-publics
maven-yuceyi,clubfactory
maven-publics
http://nexus.huoli101.com/repository/maven-public/
其中maven-publics是我们新仓库地址的id,maven-yuceyi, clubfactory是旧仓库地址的id。如果想要用新仓库地址代理所有仓库请求,那么设置为 即可。

方案三 扩展maven-resolver-impl实现忽略特定仓库
对于解决旧仓库地址问题,使用mirrorOf已经足够,但是我们希望拓展一下思路,能不能让maven通过某些参数来指定在构建时忽略特定仓库,这样操作起来将更加方便,扩展性也更强。
为此,我在网上搜寻了好久,包括也去maven官网看了,都没有找到类似的配置。于是就有了想法,自己改造一下,支持这个小功能。
从DEBUG日志入手,找到了在下载依赖时所处理的逻辑是在 这个jar包,而这个jar包是在 Maven Artifact Resolver 这个开源项目下(),因此,只要实现逻辑重新打包,最后替换掉 ${M2_HOME}/lib/下的maven-resolver-impl-xxx.jar即可。
实现逻辑很简单,就是支持系统属性或者环境变量配置忽略的仓库id列表,在下载jar包或者更新metadata信息时不使用忽略的仓库即可。
step1 下载源码
下载maven-resolver项目源码,切换到maven-resolver-1.6.x分支。
这里需要注意下版本兼容问题,我使用的是3.8.2版本maven,依赖的是 maven-resolver-impl-1.6.3.jar,所以我选择给予1.6.x分支进行扩展。
step2 增加定制逻辑
找到需要扩展逻辑的类 以及
在DefaultArtifactResolver类的performDownloads()方法中添加如下代码:

在DefaultMetadataResolver类的ResolveTask的run()方法中添加以下代码:

其中Utils.getIgnoredRepositories()代码如下:
/**
* add by xiaojiang : ignore specific repos
*
* get ignored repositories from system properties or system environment
* e.g.
* -Dmaven.custom.repository.ignored=ignored-repo1,ignored-repo2
* or
* export MAVEN_CUSTOM_REPOSITORY_IGNORED=ignored-repo3
*
* @return a set of ignored repository id
*/
public static Set getIgnoredRepositories()
{
Set ignoredRepoIds = new HashSet<>( 8 );
String ignoredRepoFromSystemProperties = System.getProperty( "maven.custom.repository.ignored" );
String ignoredRepoFromSystemEnvs = System.getenv( "MAVEN_CUSTOM_REPOSITORY_IGNORED" );
if ( ignoredRepoFromSystemProperties != null )
{
ignoredRepoIds.addAll( Arrays.asList( ignoredRepoFromSystemProperties.split( "," ) ) );
}
if ( ignoredRepoFromSystemEnvs != null )
{
ignoredRepoIds.addAll( Arrays.asList( ignoredRepoFromSystemEnvs.split( "," ) ) );
}
return ignoredRepoIds;
}
下面就是重新打包(这里需要注意下,maven项目有一致的Check-style检查,打包如果出现check-style失败,仔细按提示修改下即可),然后拷贝到${M2_HOME}/lib/下即可,如下图所示:

tips:
这里建议把原始maven-resolver-impl.jar包备份,以免出现问题需要回滚。
step 3 验证
这一步验证要求将 settings.xml 的mirrorOf配置恢复为只mirrorOf central,避免造成影响。
执行打包命令(-D指定系统属性):
mvn clean package -U -X -Dmaven.custom.repository.ignored=maven-yuceyi,clubfactory
或者使用环境变量:
export MAVEN_CUSTOM_REPOSITORY_IGNORED=maven-yuceyi,clubfactory
mvn clean package -U -X
此时查看debug日志会发现,配置的仓库列表确实被忽略,如下所示:

这里我们细心注意下图中的日志会发现,在下载正式包时,会根据候选仓库列表(clubfactory, maven-publics, maven-yuceyi)依次进行尝试,直到下载成功。上图中clubfactory仓库被忽略,直接跳过,尝试从maven-publics下载,下载成功后即不再尝试maven-yuceyi仓库,所以从图中无法看出打印忽略maven-yuceyi仓库的日志。
而上图在下载SNAPSHOT包时(或者构建时强制指定 -U 选项),会向所有候选仓库发送下载请求/更新metadata请求(这是为了保证本地拉取到的SNAPSHOT包是所有仓库中最新的),这时可以看到 clubfactory仓库、maven-yuceyi仓库都被忽略了,只有maven-publics仓库下载成功。
至此,我们通过扩展maven-resolver-impl实现了一个新功能:可以通过系统属性或者环境变量指定忽略某些仓库。
各解决方案比较
从上述解决忽略指定仓库问题的方案来看,各有各的特点,大家可以根据具体情况选择使用。
方案一 设置连接超时
这个方案操作简单,但没有根本解决问题,maven仍然尝试连接并且会有很多异常日志出现。
方案二 使用mirrorOf机制
该方案可以根本解决问题,尤其对于很多使用单个仓库作为全公司私服的情况,使用mirrorOf * 尤其方便,唯一的缺点是频繁修改settings.xml文件还是挺累人。
方案三 扩展maven-resolver-impl
这个方案增加了新功能,支持通过系统属性或者环境变量来指定忽略某些仓库,使用简单方便,扩展性好,缺点是需要自行实现新功能并替换maven依赖的jar包,并且在升级maven版本时很容易丢失自定义jar包。
综上所述,方案二和方案三都可以采用,对于在公共机器上进行构建的场景,建议使用方案二,对于自己本地打包的情况,建议使用方案三。
总结与思考
本文针对maven私服迁移过程中出现的旧仓库地址失效需要屏蔽的情况,给出了三种解决方案并分析了其优缺点,希望能给有类似需求的读者以参考。
另外,针对在推往私服的jar包中指定仓库地址的用法,个人表示不太赞成,也不符合最佳实践,因为如果出现仓库地址迁移,不可避免会遇到拉取依赖卡住的问题,给依赖方造成不必要的麻烦。当然,这里更是给大家提了醒,拷贝maven配置时一定要弄清楚来龙去脉,否则一旦出现问题就是“窝案”。
大家对这个问题有什么看法,或者遇到类似问题有其他解决方案,欢迎留言交流~