使用 Gradle 但不使用 Java 插件構建 Java 項目

原文地址:https://blog.gaoyuexiang.cn/2020/02/22/use-gradle-to-build-java-project-without-plugin/,內容沒有差異。html

本文目標是探索在沒有使用任何額外插件的狀況下,如何使用 Gradle 構建一個 Java 項目,以此對比使用 Java 插件時獲得的好處。java

初始化項目

使用 Gradle Init 插件提供的 init task 來建立一個 Gradle 項目:git

gradle init --type basic --dsl groovy --project-name gradle-demo

運行完成後,咱們將獲得這些文件:github

❯ tree
.
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle

接下來,咱們將關注點放到 build.gradle 上面,這是接下來編寫構建腳本的地方。spring

Hello World

首先,咱們編寫一個 Java 的 HelloWorld,作爲業務代碼的表明:shell

public class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello Wrold");
  }
}

而後,將這個內容保存到 src/HelloWorld.java 文件中,不按照 maven 的約定來組織項目結構。api

編譯 Java

接着,咱們須要給咱們的構建腳本添加任務來編譯剛纔寫的 Java 文件。這裏就須要使用到 Task。關於 Task Gradle 上有比較詳細的文檔描述如何使用它:https://docs.gradle.org/curre... & https://docs.gradle.org/curre...bash

如今,咱們能夠建立一個 JavaCompile 類型的 Task 對象,命名爲 compileJavaapp

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

在上面的代碼中,咱們:maven

  1. 經過 source & include 方法指定了要被編譯的文件所在的目錄和文件的擴展名
  2. 經過 destinationDir 指定了編譯後的 class 文件的存放目錄
  3. 經過 sourceCompatibility & targetCompatibility 指定了源碼的 Java 版本和 class 文件的版本
  4. 經過 classpath 指定了編譯時使用的 classpath

那麼,接下來咱們就能夠執行 compileJava 這個任務了:

❯ gradle compileJava
❯ tree build
build
├── classes
│   └── HelloWorld.class
└── tmp
    └── compileJava
❯ cd build/classes
❯ java HelloWorld
Hello World

咱們能夠看到,HelloWorld 已經編譯成功,而且能夠被正確執行。

添加第三方依賴

在實際的項目中,不免會使用到其餘人開發的庫。要使用別人開發的庫,就須要添加依賴。在 Gradle 中添加依賴,須要作這樣四個事情:

  1. 申明 repository
  2. 定義 configuration
  3. 申明 dependency
  4. dependency 添加到 classpath

申明 repository

Gradle 中能夠定義項目在哪些 repository 中尋找依賴,經過 dependencies 語法塊申明:

repositories {
  mavenCentral()
  maven {
    url 'https://maven.springframework.org/release'
  }
}

由於 mavenCentraljcenter 是比較常見的兩個倉庫,因此 Gradle 提供了函數能夠直接使用。而其餘的倉庫則須要本身指定倉庫的地址。

申明瞭 repository 以後,Gradle 纔會知道在哪裏尋找申明的依賴。

定義 configuration

若是你使用過 maven 的話,也許 repositorydependency 都能理解,但對 configuration 卻可能感到陌生。

Configuration 是一組爲了完成一個具體目標的依賴的集合。那些須要使用依賴的地方,好比 Task,應該使用 configuration,而不是直接使用依賴。這個概念僅在依賴管理範圍內適用。

Configuration 還能夠擴展其餘 configuration,被擴展的 configuration 中的依賴,都將被傳遞到擴展的 configuration 中。

咱們能夠來建立給 HelloWorld 程序使用的 configuration

configurations {
  forHelloWorld
}

定義 configuration 僅僅須要定義名字,不須要進行其餘配置。若是須要擴展,可使用 extendsFrom 方法:

configurations {
  testHelloWorld.extendsFrom forHelloWorld
}

申明 dependency

申明 dependency 須要使用到上一步的 configuration,將依賴關聯到一個 configuration 中:

dependencies {
  forHelloWorld 'com.google.guava:guava:28.2-jre'
}

經過這樣的申明,在 forHelloWorld 這個 configuration 中就存在了 guava 這個依賴。

dependency 添加到 classpath

接下來,咱們就須要將 guava 這個依賴添加到 compileJava 這個 taskclasspath 中,這樣咱們在代碼中使用的 guava 提供的代碼就能在編譯期被 JVM 識別到。

但就像在定義 configuration 中描述的那樣,咱們須要消費 configuration 以達到使用依賴的目的,而不能直接使用依賴。因此咱們須要將 compileJava.classpath 修改爲下面這樣:

classpath = files("${buildDir}/classes", configurations.forHelloWorld)

修改 HelloWorld

完成上面四步以後,咱們就能夠在咱們的代碼中使用 guava 的代碼了:

import com.google.common.collect.ImmutableMap;
public class HelloWrold {
  public static void main(String[] args) {
    ImmutableMap.of("Hello", "World")
        .forEach((key, value) -> System.out.println(key + " " + value));
  }
}

打包

前面已經瞭解過如何進行編譯,接着咱們來看看如何打包。

Java 打包好以後,每每有兩種類型的 Jar

  1. 一種是普通的 Jar,裏面不包含本身的依賴,而是在 Jar 文件外的一個 metadata 文件申明依賴,好比 maven 中的 pom.xml
  2. 另外一種被稱做 fatJar (or uberJar) ,裏面已經包含了全部的運行時須要的 class 文件和 resource 文件。

建立普通的 Jar 文件

在這個練習中,咱們就只關注 Jar 自己,不關心 metadata 文件。

在這裏,咱們天然是要建立一個 task,類型就使用 Jar

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")
}

在這個例子中,咱們:

  1. 指定了 archiveBaseName, archiveAppendix, archiveVersion 屬性,他們和 archiveClassfier, archiveExtension 將決定最後打包好的 jar 文件名
  2. 使用 from 方法,指定要從 compileJava 的輸出中拷貝文件,這樣就隱式的添加了 jarcompileJava 的依賴
  3. 使用 include 要求僅複製 class 文件
  4. 可使用 manifestMETA-INF/MANIFEST.MF 文件添加信息
  5. setDestinationDir 方法已經被標記爲 deprecated 但沒有替代的方法

接着,咱們就可使用 jar 進行打包:

❯ gradle jar
❯ tree build
build
├── classes
│   └── HelloWorld.class
├── lib
│   └── base-name-appendix-0.0.1.jar
└── tmp
    ├── compileJava
    └── jar
        └── MANIFEST.MF
        ❯ zipinfo build/lib/base-name-appendix-0.0.1.jar
❯ zipinfo build/lib/base-name-appendix-0.0.1.jar
Archive:  build/lib/base-name-appendix-0.0.1.jar
Zip file size: 1165 bytes, number of entries: 3
drwxr-xr-x  2.0 unx        0 b- defN 20-Feb-22 23:14 META-INF/
-rw-r--r--  2.0 unx       43 b- defN 20-Feb-22 23:14 META-INF/MANIFEST.MF
-rw-r--r--  2.0 unx     1635 b- defN 20-Feb-22 23:14 HelloWorld.class
3 files, 1678 bytes uncompressed, 825 bytes compressed:  50.8%

建立 fatJar

接着,一樣使用 Jar 這個類型,咱們建立一個 fatJar 任務:

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

相比於 jar,咱們的配置變動在於:

  1. 添加 archiveClassfier 以區別 fatJarjar 產生的不一樣 jar 文件
  2. 使用 fromforHelloWorld configuration 的依賴所有解壓後拷貝到 jar 文件
  3. 指定 Main-Class 屬性,以便直接運行 jar 文件

而後咱們再執行 fatJar :

❯ gradle fatJar
❯ tree build
build
├── classes
│   └── HelloWorld.class
├── lib
│   ├── base-name-appendix-0.0.1-boot.jar
│   └── base-name-appendix-0.0.1.jar
└── tmp
    ├── compileJava
    ├── fatJar
    │   └── MANIFEST.MF
    └── jar
        └── MANIFEST.MF
❯ java -jar build/lib/base-name-appendix-0.0.1-boot.jar
Hello World

總結

經過練習在不使用 Java Plugin 的狀況下,使用 Gradle 來構建項目,實現了編譯源碼、依賴管理和打包的功能,並獲得了以下完整的 gradle.build 文件:

repositories {
  mavenCentral()
}

configurations {
  forHelloWorld
}

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

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)
}

compileJava.doLast {
  println 'compile success!'
}

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")
}

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

寫了這麼多構建腳本,僅僅完成了 Java Plugin 提供的一小點功能,傷害太明顯。

完整的例子能夠在這個 repocommit 中找到 https://github.com/kbyyd24/gr...

相關文章
相關標籤/搜索