使用 Gradle 的 Java 插件構建 Java 項目

原文地址:https://blog.gaoyuexiang.cn/2...,內容沒有差異。html

上一篇文章中,咱們在沒有使用任何插件的狀況下,練習了使用 Gradle 構建 Java 項目,最後獲得一個脆弱的構建腳本和不符合約定的目錄結構。java

對此,Gradle 使用了插件來解決這些問題。spring

插件

Gradle 中的插件,能夠給咱們帶來不少好處,包括:shell

  1. 添加 Task
  2. 添加領域對象
  3. 約定優於配置的實現
  4. 擴展 Gradle 的核心類

Gradle 將插件分爲兩類,Script Plugin & Binary Pluginsegmentfault

那些寫到單獨的 gradle 文件中,並被 build.gradle 文件使用的腳本文件,就是 Script Plugin。常見的實踐是將某一插件或某一方面的配置寫到單獨的文件中,好比 jacoco.gradle,而後經過下面的語法導入到 build.gradle 文件中:app

apply from: file("$projectDir/gradle/jacoco.gradle")

而常見的 javaidea 這樣的 core Pluginorg.springframework.boot 等能夠在 https://plugins.gradle.org/ 找到的插件,就是 Binary Plugin,它們經過 plugins{} 語法塊引入:maven

plugins {
  id 'java'
  id 'org.springframework.boot' version '2.2.4.RELEASE'
}

接下來,咱們接着上一篇文章的例子,使用 Java Plugin 來改造咱們的構建腳本。ide

改造 Hello World

Java 插件的文檔:https://docs.gradle.org/curre...工具

Import Java Plugin

如上所述,咱們使用 Java Plugin 須要先導入它:性能

plugins {
  id 'java'
}

由於 Java 插件是 Gradle 提供的核心插件,它是和 Gradle 版本綁定的,因此不須要使用 version 參數。

SourceSet

引入 Java 插件後,咱們先來了解一個核心概念:SourceSet。這是 Java 插件引入的概念,每個 SourceSet 都包含了一組相關的資源。默認狀況下,一個 SourceSet 對應 src 目錄下的一個目錄,目錄名稱就是 SourceSet 的名稱;目錄下會有一個 java 目錄和一個 resources 目錄。根據約定,這兩個目錄分別是存放 java 文件的目錄和存放配置等資源文件的目錄。

SourceSet 還有更多的信息能夠配置,參見:https://docs.gradle.org/curre...:java_source_sets

Java 插件還默認配置好了兩個 SourceSet,分別是 main & test。因此在使用 Java 插件後,無需任何配置,就能夠獲得約定的目錄結構:

❯ tree src
src
├── main
│   ├── java
│   └── resources
└── test
    ├── java
    └── resources

因此,咱們須要將 HelloWorld.javasrc 目錄移動到符合約定的 src/main/java 目錄下:

❯ tree src
src
└── main
    └── java
        └── HelloWorld.java

Task

Java 插件引入的 Task

接着咱們來看看 Task 須要作哪些修改。

Java 插件引入了下面的這些 Task,而且添加了依賴關係:

其中有四個 task 是由 base plugin 添加的:clean, check, assemblebuild

其中,check, assemblebuildlifecycle task,自己不執行任務,只是定義了執行它們時應該執行什麼樣的任務:

  • check:聚合全部進行驗證操做的 task ,好比測試
  • assemble:聚合全部會產生項目產出物的 task,好比打包
  • build:聚合前面兩個 task

其餘的 task 中,很容易發現,compileJavacompileTestJavaprocessResourcesprocessTestResourcesclassestestClasses 命名相似。實際上,每一對 task 表達的是一樣的含義,只是一個針對 main sourceSet,一個針對 test sourceSet 而已。若是你建立了一個自定義的 SourceSet,那 Java 插件會自動的添加 compileSourceSetJavaprocessSourceSetResourcessourceSetClasses,其中的 sourceSet 就是 SourceSet.name

  • compileJava:編譯該 sourceSet下的 java 文件
  • processResource:將該 sourceSet 中的資源文件複製到 build 目錄中
  • classes:準備打包和執行須要的 class 文件和資源文件
注意,執行測試是 test 任務,它沒有由於添加 sourceSet 而自動添加 sourceSetTest 方法。由於自定義的 SourceSet 不必定是組件測試之類的不一樣類別的測試。因此,若是你添加了這樣的 SourceSet,須要本身手動編寫 Test 類型的測試 task

改進 Hello World

由上面的瞭解可知,Java 插件已經爲咱們添加了 compileJavajar 這兩個 task,因此咱們不須要再建立這樣的 task。可是咱們仍是能夠對這些 task 進行配置。

好比,咱們仍然但願控制 jar 產出的文件名,那咱們的腳本就能夠改爲這樣:

// task compileJava(type: JavaCompile) {
//   source fileTree("$projectDir/src")
//   include "**/*.java"
//   destinationDir = file("${buildDir}/classes")
//   sourceCompatibility = '1.8'
//   targetCompatibility = '1.8'
//   classpath = files("${buildDir}/classes", configurations.forHelloWorld)
// }

// tasks.create('jar', Jar)

jar {
  archiveBaseName = 'base-name'
  archiveAppendix = 'appendix'
  archiveVersion = '0.0.1'
//   from compileJava.outputs
//   include "**/*.class"
  manifest {
    attributes("something": "value")
  }
//   setDestinationDir file("$buildDir/lib")
}

其中註釋的部分能夠刪除,這裏僅僅做爲修改先後的對比。

根據 assemble 的定義,咱們的 fatJar 的輸出應當看做項目的產出物,因此須要讓 assemble 依賴於 fatJar

assemble.dependsOn fatJar

Dependency Configuration

Java 插件引入的 Configuration

上一篇文章講到,在 Gradle 中聲明依賴,須要關聯到 configurationJava 插件也提早爲咱們設計了一些 configuration,他們的主要關係能夠經過兩幅圖來表示。

main sourceSet 相關的:

其中:

  1. 灰色文字表示已經被廢棄的 configuration
  2. 綠色表示用於聲明依賴的 configuration
  3. 藍灰色表示給 task 使用的 configuration
  4. 淺藍色表示 task

由這個圖,咱們就能看出聲明到不一樣 configuration 中的依賴最終會在什麼地方使用到。

test sourceSet 相關的:

其中的字體和顏色與上一張圖一致。

咱們能夠看到,除去 compile, implementation, runtimerumtimeOnly,其餘的 configuration 與上圖幾乎一致。這裏畫出他們,僅僅是爲了展現出擴展關係而已。

若是你使用過之前版本的 Gradle,想必會比較好奇爲何 Compile 會被廢棄。這實際上是出於構建工具的性能的考慮,關閉掉沒必要要的傳遞依賴。

你也許也發現了,和 task 同樣,有一些名稱相近的 configuration,因此很天然的推測:添加了自定義的 SourceSet 後,Java 插件會自動的添加一些 configuration。這些 sourceSet configuration 均可以在 Java 插件的頁面上找到。

改進 Hello World

首先,咱們能夠直接使用 Java 插件提供的 implementation,而不須要本身建立任何 configuration:

// configurations {
//   forHelloWorld
// }

dependencies {
  // forHelloWorld group: 'com.google.guava', name: 'guava', version: '28.2-jre'
  implementation group: 'com.google.guava', name: 'guava', version: '28.2-jre'
}

一樣,註釋只是爲了對比。

接着,咱們的 fatJar 也不能再使用 forHelloWorld 這個 configuration,但也不能直接使用 implementation,而應該使用 runtimeClasspath 這個給 task 消費的、語義更符合咱們使用目標的 configuration

task('fatJar', type: Jar) {
  archiveBaseName = 'base-name'
  archiveAppendix = 'appendix'
  archiveVersion = '0.0.1'
  archiveClassifier = 'boot'
  from compileJava
  // from configurations.forHelloWorld.collect {
  from configurations.rumtimeClasspath.collect {
    it.isDirectory() ? it : zipTree(it)
  }
  manifest {
    attributes "Main-Class": "HelloWorld"
  }
  setDestinationDir file("$buildDir/libs")
}

總結

通過使用 Java 插件,並對構建腳本的修改,咱們獲得了更具備魯棒性、實現了約定優於配置的構建腳本。

完整的腳本以下:

plugins {
  id 'java'
}

repositories {
  mavenCentral()
}

dependencies {
  implementation group: 'com.google.guava', name: 'guava', version: '28.2-jre'
}

compileJava.doLast {
  println 'compile success!'
}

jar {
  archiveBaseName = 'base-name'
  archiveAppendix = 'appendix'
  archiveVersion = '0.0.1'
  manifest {
    attributes("something": "value")
  }
}

task('fatJar', type: Jar) {
  archiveBaseName = 'base-name'
  archiveAppendix = 'appendix'
  archiveVersion = '0.0.1'
  archiveClassifier = 'boot'
  from compileJava
  from configurations.runtimeClasspath.collect {
    it.isDirectory() ? it : zipTree(it)
  }
  manifest {
    attributes "Main-Class": "HelloWorld"
  }
  setDestinationDir file("$buildDir/libs")
}

assemble.dependsOn(fatJar)
相關文章
相關標籤/搜索