29、迁移实战:从Maven迁移到Gradle(maven-to-gradle工具)、从Ant迁移、混合项目
说实话,做构建迁移这件事,很多团队一听就头大。
我见过不少项目,Maven 用了五六年,pom.xml 堆了上千行。突然说要换 Gradle,第一反应都是:「能跑就行,别折腾。」
但真等你上手了会发现——迁移没那么可怕,关键是找对路子。今天我就把从 Maven 迁移、从 Ant 迁移,以及混合项目怎么搞,一次性讲清楚。
核心观点:迁移不是重写,是「翻译 + 优化」。保留原有构建逻辑,逐步替换成 Gradle 的 DSL 表达。
一、从 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 迁移的核心思路
- 把 Ant 的 target 映射成 Gradle 的 task——每个 target 就是一个 task,依赖关系用
dependsOn表达 - 把 Ant 的 property 改成 Gradle 的 ext 变量——或者用
project.property读取 - 文件操作直接用 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 引入
四、迁移后的验证清单
迁移完了,怎么知道对不对?我一般会跑一遍这个清单:
- 编译是否通过——
gradle build无报错 - 测试是否通过——
gradle test结果和 Maven/Ant 一致 - 产物是否一致——对比 jar/war 的 MD5 值
- 依赖是否完整——
gradle dependencies检查有没有冲突 - 增量构建是否生效——改一个文件,看是不是只重新编译受影响的部分
最后说一句:迁移不是目的,更好的构建体验才是。Gradle 的增量编译、构建缓存、并行执行,这些才是你迁移后真正能享受到的红利。别为了迁移而迁移,要想清楚「为什么要换」。
公众号:蓝海资料掘金营,微信 deep3321