9、文件操作API:文件集合(FileCollection)、文件树(FileTree)、复制与归档任务
说实话,Gradle 里文件操作这块,是我早期踩坑最多的地方。你想想看,一个构建工具,天天跟文件打交道——源码、资源、配置文件、打包产物……要是文件操作搞不明白,那构建脚本写起来真是寸步难行。
我个人习惯把 Gradle 的文件 API 分成三个层次:文件集合、文件树、复制与归档任务。咱们一层一层剥开看。
9.1 文件集合(FileCollection)——最基础的文件容器
FileCollection 说白了就是一个文件列表。它不关心目录结构,只关心「有哪些文件」。
我刚开始用 Gradle 时,以为文件操作就是写写 file() 和 files()。后来才发现,FileCollection 的延迟计算特性才是精髓。
看个例子:
// 创建一个文件集合
def myFiles = files('src/main/java', 'src/main/resources')
// 可以追加
myFiles += files('src/test/java')
// 可以过滤
def javaFiles = myFiles.filter { it.name.endsWith('.java') }
// 可以遍历
myFiles.each { file ->
println "文件: ${file.name}"
}
嗯,这里要注意:files() 返回的是 ConfigurableFileCollection,它是可变的。但如果你用 fileCollection() 从任务获取,那通常是只读的。
9.2 文件树(FileTree)——带目录结构的文件集合
FileTree 是 FileCollection 的子接口。它多了一个东西:目录结构信息。你可以按目录遍历,可以匹配 Ant 风格的路径模式。
说白了,FileCollection 是「平铺」的,FileTree 是「树形」的。
// 从目录创建文件树
def tree = fileTree('src/main/java') {
include '**/*.java'
exclude '**/test/**'
}
// 从 ZIP 文件创建文件树
def zipTree = zipTree('build/dist/myapp.zip')
// 遍历文件树(带目录信息)
tree.visit { fileDetails ->
println "${fileDetails.relativePath} - ${fileDetails.file.size()} bytes"
}
我曾经在做一个多模块项目时,需要把每个模块的特定资源文件提取出来。用 FileTree 的 matching 方法配合 include/exclude,一行代码就搞定了过滤逻辑。
9.3 复制任务(Copy)——最常用的文件操作任务
Copy 任务是我用得最多的 Gradle 内置任务。它的设计很优雅:声明式配置,你只需要告诉它「从哪来」「到哪去」「要什么」。
task copyResources(type: Copy) {
from 'src/main/resources'
into 'build/resources'
include '**/*.properties'
exclude '**/secret.properties'
// 还可以重命名
rename { fileName ->
fileName.replace('.properties', '.config')
}
// 或者过滤内容
filter { line ->
line.replace('${version}', project.version.toString())
}
}
你想想看,如果用 Java 的 Files.copy() 实现同样的功能,得写多少代码?Gradle 的 Copy 任务把这些都封装好了。
CopySpec 接口。这个接口支持嵌套,你可以把多个复制规范组合在一起。
task deploy(type: Copy) {
// 第一组:配置文件
into 'build/deploy/config'
from('src/main/config') {
include '*.xml'
}
// 第二组:可执行文件
into 'build/deploy/bin'
from('build/libs') {
include '*.jar'
}
// 第三组:文档
into 'build/deploy/docs'
from('src/docs') {
include '*.md'
}
}
我个人特别喜欢这种嵌套写法。它让复杂的复制逻辑变得清晰可读。每个 from 块都可以有自己的 include/exclude,互不干扰。
9.4 归档任务(Archive)——打包的艺术
归档任务是 Copy 任务的「升级版」。它把复制结果直接打包成 ZIP、TAR、JAR 等格式。
Gradle 内置了三种归档任务:
| 任务类型 | 默认扩展名 | 典型用途 |
|---|---|---|
| Zip | .zip | 通用打包 |
| Tar | .tar.gz | Linux 环境 |
| Jar | .jar | Java 库/应用 |
task dist(type: Zip) {
archiveFileName = "myapp-${version}.zip"
destinationDirectory = file('build/dist')
from 'build/libs'
from('src/main/scripts') {
into 'bin'
fileMode = 0755
}
from('src/main/config') {
into 'conf'
}
}
为什么说归档任务是 Copy 的升级版?因为它继承了 CopySpec 的所有能力——过滤、重命名、嵌套目录结构,全部支持。你只需要在最后指定一下归档格式就行。
9.5 知识体系总览
下面这张图是我梳理的文件操作 API 核心逻辑,你看一眼就能明白它们之间的关系:
9.6 实战:组合使用
光讲理论没意思。我分享一个真实项目中的场景:
当时我们需要构建一个微服务部署包。要求是:把编译好的 JAR、配置文件、启动脚本打包成一个 ZIP,同时把配置文件中的占位符替换成实际值。
task buildDeployPackage(type: Zip) {
archiveFileName = "service-${version}.zip"
destinationDirectory = file("${buildDir}/deploy")
// 1. 复制 JAR 包
from tasks.named('bootJar') {
into 'lib'
}
// 2. 复制启动脚本并设置权限
from('src/deploy/scripts') {
into 'bin'
include 'start.sh', 'stop.sh'
fileMode = 0755
}
// 3. 复制配置文件并替换占位符
from('src/deploy/config') {
into 'conf'
include '*.yml'
filter { line ->
line
.replace('${app.version}', version)
.replace('${app.port}', port.toString())
}
}
// 4. 复制文档
from('docs') {
into 'docs'
include 'README.md'
}
}
你看,一个任务就把所有事情搞定了。这就是 Gradle 文件操作 API 的魅力——组合能力强,表达力高。
filter 时要注意编码问题。我曾经在 Windows 上构建时,因为默认编码不是 UTF-8,导致配置文件中的中文乱码。建议显式指定编码:filteringCharset = 'UTF-8'。
9.7 小结
文件操作 API 是 Gradle 构建脚本的「基础设施」。掌握了 FileCollection、FileTree、Copy 和 Archive,你就能应对绝大多数文件处理场景。
我个人觉得,理解「延迟计算」和「声明式配置」这两个理念,比记住具体 API 更重要。理念通了,API 只是工具。
嗯,文件操作这块就聊到这儿。记住:能用 CopySpec 组合解决的问题,不要写循环。Gradle 的设计者已经帮你考虑好了。