写点什么

maven 如何忽略指定的远程仓库

用户头像
小江
关注
发布于: 刚刚
maven如何忽略指定的远程仓库

问题背景

最近在进行云平台提供商迁移的过程中遇到一个跟 maven 仓库相关的有趣的问题,跟大家分享一下。

由于公司 nexus 私服迁移到新机器上,启用了新的地址,原来的地址下线,在 Jenkins 打包机器上重新配置好 settings.xml 文件后,发现还是有部分应用在拉取依赖包时仍然访问老的 nexus 私服地址,导致卡住进而超时,如下图所示:

设置nexus新地址后仍然访问已下线地址


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

业务方jar包指定仓库地址


maven 远程仓库的顺序

遇到这个问题的时候其实有点诧异,因为按照官方文档,应该是全局 settings.xml 或者 .m2/settings.xml 中配置的仓库地址优先,不应该出现类似问题才对。


  1. effective settings:Global settings.xmlUser settings.xml

  2. local effective build POM:Local pom.xmlParent POMs, recursivelySuper POM

  3. effective POMs from dependency path to the artifact.


可真实情况是,在下载依赖包时,依赖包中定义的仓库地址也会生效,并且会和 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包中指定仓库地址


向仓库列表同时发起下载请求及更新metadata请求


那么面对这种旧仓库地址已经下线,而很多 jar 包仍然依赖着老地址的尴尬局面,该如何解决呢?总不能让业务方都重新发布一遍 jar 包,替换旧仓库地址吧,这样做法工作量巨大,而且很多 jar 包都没人维护了,所以必须寻找其他解决方案。

解决方案

从前面的分析得知,要想解决上述问题,必须在拉取依赖包时能屏蔽某些不想使用的仓库地址。为此,我们总结了三种方案,供大家参考。

方案一 设定连接超时时间

我们知道 maven 底层是通过 http client 来拉取数据的,一定是有连接建立超时时间或者数据读取时间限制,那么对于想要屏蔽的地址,只要我们将该时间设置的尽可能短,那么就可以达到忽略某些仓库地址的目的。


比如,对于已经下线的 maven-yuceyi 和 clubfactory 仓库,我们在 settings.xml 中进行如下设置(参考官方文档):

<server>    <id>clubfactory</id>    <username>admin</username>    <password>xxx</password>    <configuration>        <httpConfiguration>            <all>                <connectionTimeout>1000</connectionTimeout>                <readTimeout>5000</readTimeout>            </all>        </httpConfiguration>    </configuration></server>
<server> <id>maven-yuceyi</id> <username>admin</username> <password>xxx</password> <configuration> <httpConfiguration> <all> <connectionTimeout>1000</connectionTimeout> <readTimeout>5000</readTimeout> </all> </httpConfiguration> </configuration></server>
复制代码


那么对于已经下线的旧仓库地址来说,在 1000ms 内就会连接失败,可以解决一直卡住的问题,如下图所示:

指定仓库连接超时时间后构建结果(1)


指定仓库连接超时时间后构建结果(2)


这里已经可以解决旧仓库地址无法连接导致卡住的问题,但是不太优雅,因为构建日志中会出现很多异常日志。那么有没有既可以屏蔽旧仓库地址,又不引入异常日志的方案呢?答案是肯定的,这就是接下来介绍的方案二。


方案二 使用 mirrorOf 机制

我们知道,在 maven 的 settings.xml 配置中,有一个 mirror 配置,用来把某些仓库的请求都由指定的仓库地址进行代理(参考官方文档)。那么对于已经下线的旧仓库地址来说,只要我们把它们配置为使用新仓库地址进行 mirror,就实现了屏蔽某些仓库的目的,并且不会出现异常,因为不会向旧地址发起请求。


mirror 配置如下:

<mirror>    <id>maven-publics</id>    <mirrorOf>maven-yuceyi,clubfactory</mirrorOf>    <name>maven-publics</name>    <url>http://nexus.huoli101.com/repository/maven-public/</url></mirror>
复制代码

其中 maven-publics 是我们新仓库地址的 id,maven-yuceyi, clubfactory 是旧仓库地址的 id。如果想要用新仓库地址代理所有仓库请求,那么设置为 <mirrorOf>*</mirrorOf> 即可。

使用mirrorOf代理被屏蔽的仓库


方案三 扩展 maven-resolver-impl 实现忽略特定仓库

对于解决旧仓库地址问题,使用 mirrorOf 已经足够,但是我们希望拓展一下思路,能不能让 maven 通过某些参数来指定在构建时忽略特定仓库,这样操作起来将更加方便,扩展性也更强。


为此,我在网上搜寻了好久,包括也去 maven 官网看了,都没有找到类似的配置。于是就有了想法,自己改造一下,支持这个小功能。


从 DEBUG 日志入手,找到了在下载依赖时所处理的逻辑是在 maven-resolver-impl 这个 jar 包,而这个 jar 包是在 Maven Artifact Resolver 这个开源项目下(github镜像),因此,只要实现逻辑重新打包,最后替换掉 ${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 增加定制逻辑

找到需要扩展逻辑的类org.eclipse.aether.internal.impl.DefaultArtifactResolver 以及 org.eclipse.aether.internal.impl.DefaultMetadataResolver


在 DefaultArtifactResolver 类的 performDownloads()方法中添加如下代码:

下载依赖包时忽略特定仓库逻辑


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

metadata处理增加忽略特定仓库逻辑


其中 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<String> getIgnoredRepositories()    {        Set<String> 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,clubfactorymvn 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 配置时一定要弄清楚来龙去脉,否则一旦出现问题就是“窝案”。


大家对这个问题有什么看法,或者遇到类似问题有其他解决方案,欢迎留言交流~

发布于: 刚刚阅读数: 2
用户头像

小江

关注

~做一个安静的码男子~ 2019.02.11 加入

软件工程师,目前在电商公司做研发效能平台,中间件维护开发相关工作

评论

发布
暂无评论
maven如何忽略指定的远程仓库