在 Java 建構工具的演進歷程中,Ant 開啟了自動化建構的先河,Maven 隨後以「慣例優於配置」(Convention over Configuration)的理念、強大的依賴管理與專案結構標準化,確立了事實標準的地位。然而,隨著專案規模膨脹,Maven 繁瑣的 XML 配置、僵化的建構生命週期以及難以擴展的特性,逐漸成為開發團隊的痛點。
Gradle 作為新一代建構工具,榮獲 2010 Springy 大獎並入圍 2011 JAX 最佳 Java 技術創新獎。它基於 Groovy 語言構建領域特定語言(DSL),既繼承了 Maven 的優勢,又以靈活、簡潔的語法解決了 XML 的冗長與不靈活。正如 Eugene Dvorkin 所言:「Gradle 給你所需的自由,讓你構建易描述、可維護、簡潔、高效能的專案。」筆者在近半年大型 Java 專案的實戰中,深刻體會到 Gradle 在建構流程中的簡潔與強大。
Hibernate 專案負責人 Steve Ebersole 在將建構腳本從 Maven 遷移至 Gradle 時指出:Maven 對多模組建構的支援存在短板。在微服務架構盛行的今天,單一專案包含多個模組已成常態。Gradle 原生支援多模組,並提供豐富機制簡化建構腳本。在 Gradle 中,每個模組對應一個子專案(subproject),父專案負責頂層協調,子專案描述具體模組。
Gradle 遵循「慣例優於配置」原則。父專案根目錄下的 settings.gradle 定義了參與建構的子專案。在初始化階段,Gradle 依據此檔案識別子專案,並為每個子專案初始化 Project 物件,可在建構腳本中透過 project(':sub-project-name') 引用。
典型目錄結構要求子模組置於父專案根目錄,特殊結構可在 settings.gradle 中自訂路徑。筆者所在專案包含核心業務 core、遺留 EJB 模組 enterprise-beans、兩個 Web 專案 cis-war 與 admin-war、JAXB 生成專案 jaxb、EAR 打包專案 ear,以及存放配置檔的 config 目錄(非建構模組)。對應的 settings.gradle 如下:
include 'core', 'enterprise-beans', 'cis-war', 'admin-war', 'jaxb', 'ear'
僅將需建構的模組納入,config 目錄自然排除。
大型專案中,子專案間必存在共同配置。Gradle 提供三種層級的共享機制:
allprojects { apply plugin: 'idea' }
subprojects {
apply plugin: 'java'
repositories { mavenCentral() }
ext {
guavaVersion = '14.0.1'
junitVersion = '4.10'
}
dependencies {
compile "com.google.guava:guava:${guavaVersion}"
testCompile "junit:junit:${junitVersion}"
}
}
cis-war 與 admin-war 需 War 外掛:configure(subprojects.findAll { it.name.contains('war') }) {
apply plugin: 'war'
}
每個子專案仍有專屬配置(如特定依賴、自訂 Task)。Gradle 提供兩種方式:
build.gradle 中透過 project(':core') 針對性配置。project(':core') {
ext { hibernateVersion = '4.2.1.Final' }
dependencies { compile "org.hibernate:hibernate-core:${hibernateVersion}" }
}
build.gradle,各自管理配置。// core/build.gradle
ext { hibernateVersion = '4.2.1.Final' }
dependencies { compile "org.hibernate:hibernate-core:${hibernateVersion}" }
經驗建議:模組少、配置簡單的小型專案,推薦方式一,集中管理;模組多、配置複雜的大型專案,方式二更優,職責分離,便於維護。
Gradle 允許在父專案定義通用方法,子專案直接調用。例如定義從命令列讀取屬性(含預設值)的方法:
def defaultProperty(propertyName, defaultValue) {
return hasProperty(propertyName) ? project[propertyName] : defaultValue
}
這純粹是 Groovy 程式碼,可讀性極佳,子專案可直接呼叫。
針對開發、測試、生產等環境,Gradle 提供多種機制實現差異化打包與資料庫初始化。
方式一:傳統 .properties 檔案
提供 development.properties、test.properties、production.properties,啟動時傳入 -Pprofile=development 指定環境。建構腳本載入邏輯:
ext { profile = project['profile'] }
def loadProperties() {
def props = new Properties()
new File("${rootProject.projectDir}/config/${profile}.properties")
.withInputStream { stream -> props.load(stream) }
props
}
方式二:Groovy 配置檔(推薦)
定義 config.groovy,結構清晰,支援巢狀分組:
environments {
development { jdbc { url = 'dev_db'; user = 'u'; password = 'p' } }
test { jdbc { url = 'test_db'; user = 'u'; password = 'p' } }
production { jdbc { url = 'prod_db'; user = 'u'; password = 'p' } }
}
載入方式:
def loadGroovy() {
def configFile = file('config.groovy')
new ConfigSlurper(profile).parse(configFile.toURL()).toProperties()
}
透過 jdbc.url 即可取值。Groovy 寫法結構化強,維護性優於扁平的 properties,建議新專案採用;遺留專案沿用 properties 亦無妨。
配置檔中以 @key@ 標記佔位符(如 jdbc.url=@jdbc.url@)。Gradle 內建 processResources Task 可處理 src/main/resources 下檔案:
processResources {
from(sourceSets.main.resources.srcDirs) {
filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: loadGroovyConfig())
}
}
若配置檔集中管理於專案根目錄 config 目錄(筆者專案採此架構),需自訂 Task:
task replace(type: Sync) {
def configHome = "${project.rootDir}/config"
from(configHome) {
include '**/*.properties', '**/*.xml'
filter org.apache.tools.ant.filters.ReplaceTokens, tokens: loadGroovyConfig()
}
into "${buildDir}/resources/main"
}
此 Sync Task 將替換後檔案輸出至建構目錄,再讓打包 Task 依賴它即可。
當環境間不僅數值不同,連配置檔結構皆異(如開發環境用 Stub 模擬 Web Service,測試/生產用真實端點),單純替換無能為力。Gradle 允許按環境指定資源目錄:
sourceSets {
main {
resources {
srcDir "config/application/spring/${profile}", "config/application/properties/${profile}"
}
}
}
目錄結構範例:

打包時自動採用對應環境目錄下的檔案。
Gradle 無內建資料庫初始化 Task,但可利用 Groovy 的 groovy.sql.Sql 執行 SQL 腳本:
groovy.sql.Sql oracleSql = Sql.newInstance(
props.getProperty('database.connection.url'),
props.getProperty('database.userid'),
props.getProperty('database.password'),
props.getProperty('database.connection.driver')
)
try {
new File(script).text.split(";").each {
logger.info it
oracleSql.execute(it)
}
} catch (Exception e) {
// 關鍵 SQL 失敗可拋出異常中止
}
注意:資料庫驅動需在 Classpath 中。亦可使用 Ant 的 sql Task,但非必要不建議,Groovy 方式更原生。
除人工 Code Review,自動化靜態分析是品質保障關鍵。Gradle 官方整合主流工具:
apply plugin: 'checkstyle',可自訂 configFile。apply plugin: 'findbugs',可配置報告輸出、掃描範圍。apply plugin: 'jdepend'。apply plugin: 'pmd'。套用後執行 gradle check 即可跑全套檢查,報告產出於各模組 build/reports。若需 Gradle 未內建工具(如 Cobertura 覆蓋率),可自行開發外掛或封裝 Ant Task 為 Gradle 外掛複用。
Gradle 沿用 Maven 坐標(group:name:version),支援多種倉庫。依賴宣告極簡潔:
dependencies {
compile 'org.hibernate:hibernate-core:3.6.7.Final'
testCompile 'junit:junit:4.11'
}
多依賴可合併:
compile(
'org.hibernate:hibernate-core:3.6.7.Final',
'org.springframework:spring-context:3.1.4.RELEASE'
)
倉庫找不到的 Jar(如資料庫驅動)可置於專案 lib 目錄,透過 fileTree 納入:
compile fileTree(dir: "${rootProject.projectDir}/lib", include: '*.jar')
多模組專案中,模組間依賴透過 project(':moduleName') 宣告。例如 cis-war 依賴 core:
dependencies {
compile 'org.hibernate:hibernate-core:3.6.7.Final', project(':core')
}
建構時 Gradle 自動將 core 納入 Classpath。
使用非官方外掛(如社區提供的 gradle-cucumber-plugin)時,需在 buildscript 宣告:
buildscript {
repositories { mavenCentral() }
dependencies { classpath "gradle-cucumber-plugin:gradle-cucumber-plugin:0.2" }
}
apply plugin: com.excella.gradle.cucumber.CucumberPlugin
複雜腳本可按功能拆分為多個 .gradle 檔,再在主檔引入:
apply from: "jetty.gradle" apply from: "jboss.gradle"
rootDir:子專案腳本中訪問根專案路徑。rootProject:子專案中獲取父專案 Project 物件。task wrapper(type: Wrapper) {
gradleVersion = '1.0'
archiveBase = 'PROJECT'
archivePath = 'gradle/dists'
}
執行 gradle wrapper 產生 gradlew、gradle/wrapper 目錄。團隊統一用 gradlew 執行 Task,首次自動下載指定版本,確保環境一致。
專案根目錄 gradle.properties 定義屬性(如倉庫帳密),建構腳本用 ${username} 讀取。此檔不提交版本庫,各成員自行維護,安全便捷。
本文僅呈現筆者在大型專案中應用 Gradle 的核心經驗。Gradle 功能遠不止於此(外掛開發、多語言建構等),官方文檔極為詳盡,值得深入研讀。同時,Gradle 基於 Groovy,學習過程中自然掌握 Groovy 技能,可謂一舉兩得。