29、迁移实战:从Maven迁移到Gradle(maven-to-gradle工具)、从Ant迁移、混合项目

说实话,做构建迁移这件事,很多团队一听就头大。

我见过不少项目,Maven 用了五六年,pom.xml 堆了上千行。突然说要换 Gradle,第一反应都是:「能跑就行,别折腾。」

但真等你上手了会发现——迁移没那么可怕,关键是找对路子。今天我就把从 Maven 迁移、从 Ant 迁移,以及混合项目怎么搞,一次性讲清楚。

核心观点:迁移不是重写,是「翻译 + 优化」。保留原有构建逻辑,逐步替换成 Gradle 的 DSL 表达。

构建迁移路线图 Maven 项目 pom.xml → build.gradle Ant 项目 build.xml → 自定义任务 混合项目 Maven + Ant 共存 Gradle 构建体系 统一 DSL + 任务编排 + 依赖管理 自动转换 maven-to-gradle 工具 手动迁移 逐模块改写 + 测试 混合过渡 Gradle 调用 Ant 任务

一、从 Maven 迁移:maven-to-gradle 工具

我个人最推荐的方式——先用工具自动转,再手动微调

Gradle 官方提供了一个叫 maven-to-gradle 的脚本,能帮你把 pom.xml 翻译成 build.gradle。说白了,它就是个「翻译器」。

使用方式:

# 安装工具
gradle init --type pom

# 或者用独立脚本
wget https://raw.githubusercontent.com/gradle/maven-to-gradle/master/maven2gradle
chmod +x maven2gradle
./maven2gradle /path/to/pom.xml

跑完之后,你会得到一个 build.gradle 文件。但注意——别指望它一次完美

我曾经在一个 30 多个模块的项目上跑这个工具,生成的脚本里依赖版本号全对,但有几个 exclusions 没处理好,导致 jar 冲突。嗯,这种坑我踩过不止一次。

迁移后需要手动检查的点

检查项 Maven 写法 Gradle 写法 注意事项
依赖管理 <dependencyManagement> dependencyManagement { } 注意 BOM 的引入方式
插件配置 <plugin> + <executions> plugins { id(...) } execution 要转成 task 配置
Profile <profiles> buildTypes / productFlavors Android 项目用 flavor,Java 用 buildType
资源过滤 <filters> + <filtering> processResources { filter(...) } 语法差异较大,建议重写

避坑指南:我曾经遇到过一个项目,Maven 里用了 maven-assembly-plugin 打胖 jar。工具转过来后,Gradle 里用的是 shadow 插件,但配置没完全对应上,导致运行时缺类。后来我手动改成了 jar { from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } } 才搞定。

二、从 Ant 迁移:更灵活,也更麻烦

Ant 的迁移和 Maven 不太一样。Maven 有固定的生命周期,Ant 几乎全是自定义任务。你想想看,每个 Ant 项目的 build.xml 长得都不一样。

所以我的建议是:不要试图自动转换 Ant,而是用 Gradle 重新组织构建逻辑

Ant 迁移的核心思路

  1. 把 Ant 的 target 映射成 Gradle 的 task——每个 target 就是一个 task,依赖关系用 dependsOn 表达
  2. 把 Ant 的 property 改成 Gradle 的 ext 变量——或者用 project.property 读取
  3. 文件操作直接用 Gradle 的 Copy、Delete 等内置任务——比 Ant 的 <copy> 简洁得多

示例对比:

// Ant 的 build.xml
<target name="compile">
    <javac srcdir="src" destdir="build/classes" classpathref="libs"/>
</target>

// 对应的 Gradle build.gradle
task compile(type: JavaCompile) {
    source = fileTree('src')
    destinationDirectory = file('build/classes')
    classpath = configurations.compileClasspath
}

你看,Gradle 的写法其实更直观。JavaCompile 这个 task 类型已经封装好了所有编译参数,你只需要告诉它「源码在哪、输出到哪、依赖是什么」就够了。

Ant 迁移的常见陷阱

  • Ant 的 <path> 结构——在 Gradle 里用 files()fileTree() 替代
  • Ant 的 <macrodef>——Gradle 里用 task type: ... 或者自定义 task 类
  • Ant 的 <condition>——Gradle 里用 onlyIf { } 控制 task 执行条件

我的经验:如果 Ant 项目里用了大量自定义 <script>(比如嵌入 JavaScript 或 Groovy),建议先保留这些脚本,用 Gradle 的 ant.importBuild 直接引入。等稳定了再逐步替换。

三、混合项目:Gradle + Maven / Ant 共存

说实话,现实中的迁移很少是一次性完成的。更多的情况是——一边用着旧的构建系统,一边慢慢往 Gradle 上靠

我参与过一个大型金融项目,Maven 和 Ant 混用了三年。最终我们用了「渐进式迁移」的策略,才平稳过渡。

策略一:Gradle 调用 Maven

如果你不想动 Maven 的 pom.xml,可以在 Gradle 里通过 maven-publish 插件直接引用 Maven 的构建产物。

// 在 Gradle 中引用 Maven 构建的 jar
dependencies {
    implementation files('libs/legacy-module.jar')  // Maven 构建好的 jar
    // 或者通过本地仓库
    implementation 'com.legacy:old-module:1.0'
}

策略二:Gradle 调用 Ant

Gradle 对 Ant 有原生支持。你可以直接在 build.gradle 里写 Ant 任务:

ant.importBuild 'build.xml'  // 直接引入整个 Ant 构建

// 然后就可以在 Gradle 里依赖 Ant 的 target
task myTask(dependsOn: 'ant.compile') {
    doLast {
        println 'Ant 编译完成,继续 Gradle 任务'
    }
}

注意:ant.importBuild 引入的 target 会变成 Gradle 的 task,但它们的执行顺序和依赖关系可能和 Ant 里不一样。我曾经遇到过 Ant 的 clean 和 Gradle 的 clean 冲突,最后不得不把 Ant 的 clean 重命名。

策略三:多模块混合

如果你的项目既有 Maven 模块又有 Gradle 模块,可以用 settings.gradle 统一管理:

// settings.gradle
rootProject.name = 'hybrid-project'
include 'module-a'       // Gradle 模块
include 'module-b'       // Gradle 模块
include 'module-c'       // 这个模块还是 Maven 构建

// 对于 Maven 模块,可以在 Gradle 中通过 maven-publish 引用
// 或者用 composite build 引入

四、迁移后的验证清单

迁移完了,怎么知道对不对?我一般会跑一遍这个清单:

  1. 编译是否通过——gradle build 无报错
  2. 测试是否通过——gradle test 结果和 Maven/Ant 一致
  3. 产物是否一致——对比 jar/war 的 MD5 值
  4. 依赖是否完整——gradle dependencies 检查有没有冲突
  5. 增量构建是否生效——改一个文件,看是不是只重新编译受影响的部分

最后说一句:迁移不是目的,更好的构建体验才是。Gradle 的增量编译、构建缓存、并行执行,这些才是你迁移后真正能享受到的红利。别为了迁移而迁移,要想清楚「为什么要换」。


公众号:蓝海资料掘金营,微信 deep3321