【Gradle教程】Gradle 入門

  • 2020 年 5 月 30 日
  • 筆記

本文為我在學習群內分享時在B站直播分享時的文檔,直播間地址 //live.bilibili.com/22263819

PS:問一下,Linux下有什麼好用的會議軟體么? 知道的朋友煩請評論告知,感謝

00.簡介

Gradle 是一種開源自動化構建工具,支援多語言環境,受 Ant、Maven 思想的影響,集二者之大成,相比 Ant 的不規範,Maven 的配置複雜、生命周期限制嚴重,Gradle 既規範也更靈活,可以使用DSL (領域特定語言,如Groovy 或 Kotlin)編寫構建腳本,腳本更短小精悍

它的特性有:

  • 高度訂製化:模組化可擴展,更靈活
  • 構建迅速:支援並行構建,自動復用之前任務構建的結果以提高效率
  • 功能強大:支援多語言環境,包含 Java、Android、C++、Groovy、Javascript 等項目的構建

Ant、Maven 有的 Gradle也有,Gradle有的它們不一定有;

Ant、Maven能幹的,Gradle 都能幹,而且幹得更好

01.安裝

Gradle 二進位包安裝流程:

  • 確保 JDK 正確安裝
  • 下載最新版 Gradle //gradle.org/releases/
  • 解壓到指定目錄
  • 設置 path
  • gradle -v 測試

以 Linux 舉例:

cd /opt
wget //services.gradle.org/distributions/gradle-6.4.1-bin.zip
sudo unzip -oq gradle-6.4.1-bin.zip #解壓到/opt/gradle-6.4.1
#切換root許可權,設置環境變數
sudo su -
cat >> /etc/profile <<EOF
export PATH=\$PATH:/opt/gradle-6.4.1/bin
export GRADLE_USER_HOME=/opt/gradle-6.4.1 #Gradle的快取會存在此目錄下的caches中
EOF
exit
source /etc/profile

#測試
hellxz@debian:~$ gradle -v

------------------------------------------------------------
Gradle 6.4.1
------------------------------------------------------------

Build time:   2020-05-15 19:43:40 UTC
Revision:     1a04183c502614b5c80e33d603074e0b4a2777c5

Kotlin:       1.3.71
Groovy:       2.5.10
Ant:          Apache Ant(TM) version 1.10.7 compiled on September 1 2019
JVM:          11.0.7 (Debian 11.0.7+10-post-Debian-3deb10u1)
OS:           Linux 4.19.0-9-amd64 amd64

02.Hello World

build.gradle

task hello {
    println 'Hello world!'
}
gradle -q hello

-q 的作用是靜默輸出,使輸出更加清晰

這裡發生了什麼? 這個構建腳本定義了一個獨立的 task, 叫做 hello, 並且加入了一個 action,當你運行 gradle hello, Gradle 執行叫做 hello 的 task, 也就是執行了你所提供的 action. 這個 action 是一個包含了一些 Groovy 程式碼的閉包(Closure)

擴展閱讀:

task塊內可以定義前置、後置執行的方法(閉包)doFirstdoLast,按字面意思來理解就可以,但要注意,定義多個doLast或doFirst無法保證執行順序

03.構建基礎

projects 和 tasks

projectstasks是 Gradle 中最重要的兩個概念。

任何一個 Gradle 構建都是由一個project或多個 projects 組成。每個 project 或許是一個 jar 包或者一個 web 應用,它也可以是一個由許多其他項目中產生的 jar 構成的 zip 壓縮包。

每個 project 都由多個 tasks 組成每個 task 都代表了構建執行過程中的一個原子性操作。如編譯,打包,生成 javadoc,發布到某個倉庫等操作。

簡單來說,project 相當於一個構建工程的一個模組,而 task 是其中一個模組的一個操作

調用Groovy

在 build.gradle (可以稱為build script,構建配置腳本) 中可以調用 Groovy 的類庫(也包含 Java 類庫),下面是示例:

build.gradle

task upper {
  String str = 'this is a simple test'
  println "原始值:" + str
  println "轉大寫後:" + str.toUpperCase()
}

執行命令 gradle -q upper

此示例中簡單地將 this is a simple test 轉成了 THIS IS A SIMPLE TEST,其中使用了 String 的 toUpperCase() 方法

Groovy 兼容 Java 語法,我們可以通過在 task 中調用 Groovy 或 Java 的方法來完成想做的操作

定義項目version/group

在 Maven 中,可以明確定義項目版本,構建時會將這個版本包含在 war 或 jar 等製品的文件名稱中,推送到 Maven 私服中也需要設置 group artifactId version 資訊,那麼 Gradle 中如何定義呢?

Gradle 中,對應 Maven 的三個參數,將 artifactId 變成了 rootProject.name,那麼只需額外定義 groupversion

build.gradle 中設置

version = "0.0.1"
group = "com.cnblogs.hellxz"

Gradle 配置中還有一個特殊的配置文件,gradle.properties,我們可以在裡邊配置變數供 build.gradle 讀取

version=0.0.1
group=com.cnblogs.hellxz

04.Java 構建入門

生成Java項目

使用 Maven 時我們可以通過以下命令來創建一個簡單的 Java 項目

mvn archetype:generate -DgroupId=xxx -DartifactId=yyy -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeCatalog=local -DinteractiveMode=false

Maven 有的 Gradle 自然也不會落下,我們可以使用 init task來初始化一個 Java 項目

$ gradle init
> Task :wrapper

Select type of project to generate:
  1: basic
  2: application
  3: library
  4: Gradle plugin
Enter selection (default: basic) [1..4] 2

Select implementation language:
  1: C++
  2: Groovy
  3: Java
  4: Kotlin
  5: Swift
Enter selection (default: Java) [1..5] 3

Select build script DSL:
  1: Groovy
  2: Kotlin
Enter selection (default: Groovy) [1..2] 1

Select test framework:
  1: JUnit 4
  2: TestNG
  3: Spock
  4: JUnit Jupiter
Enter selection (default: JUnit 4) [1..4]

Project name (default: demo):

Source package (default: demo):


> Task :init
Get more help with your project: //docs.gradle.org/5.4.1/userguide/tutorial_java_projects.html

BUILD SUCCESSFUL
2 actionable tasks: 2 executed

生成程式碼的結構如下:

init task 執行的時候,優先調用 wrapper task 來生成 gradlewgradlew.bat 以及新項目所需的程式碼結構

帶大家讀文檔 //guides.gradle.org/building-java-applications/

插件:war

  • 作用:將當前項目打成war包
  • 使用:
    1. build.gradle 中添加 apply plugin: war 或在 plugins 塊中添加 id: war
    2. gradle build,生成war到當前項目目錄根build/libs下

生成SpringBoot2項目

帶大家讀文檔並實踐 //guides.gradle.org/building-spring-boot-2-projects-with-gradle/

由於中國網路問題,需要配置中國源和插件源,這裡把本人測試時的主要部分放出來

項目結構就是官方文檔中的那樣

gradle.spring.boot.project 包下的 APP.java :

/*
 * This Java source file was generated by the Gradle 'init' task.
 */
package gradle.spring.boot.project;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@SpringBootApplication
public class App extends SpringBootServletInitializer {

    @GetMapping("/greet")
    public String getGreeting() {
        return "Hello world.";
    }

    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return super.configure(builder);
    }
}

build.gradle :

plugins {
    id 'java'
    id 'application'
    id 'org.springframework.boot' version '2.0.5.RELEASE'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
    id 'war'
}

repositories {
    maven { url '//maven.aliyun.com/repository/public/' }
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-dependencies:2.0.5.RELEASE'
    providedCompile 'org.springframework.boot:spring-boot-starter-web'
    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    components {
        withModule('org.springframework:spring-beans') {
            allVariants {
                withDependencyConstraints {
                    // Need to patch constraints because snakeyaml is an optional dependency
                    it.findAll { it.name == 'snakeyaml' }.each { it.version { strictly '1.19' } }
                }
            }
        }
    }
}
application {
    mainClassName = 'gradle.spring.boot.project.App'
}

settings.gradle :

pluginManagement {
    resolutionStrategy {
    }
    repositories {
        maven { url "//maven.aliyun.com/nexus/content/groups/public" }
        gradlePluginPortal()
    }
}

rootProject.name = 'gradle-spring-boot-project'

將SpringBoot項目打成 war 包

  • 步驟:

    • 添加 war 插件

    • 執行 gradle buildgradle bootWar (build會執行很多操作,booWar編譯後只打war包)

      • 打包出可直接執行啟動的war(內嵌tomcat)

      • 屏蔽tomcat依賴,繼承 SpringBootServletInitializer,重寫configure方法

        build.gradle

        dependencies{
        	providedCompile 'org.springframework.boot:spring-boot-starter-tomcat'
        }
        

        App.java

        @SpringBootApplication
        public class App extends SpringBootServletInitializer{
        	public static void main(String[] args){
            	SpringApplication.run(App.class, args);
            }
            protected SpringApplicationBuilder configure(SpringApplicationBuilder app){
            	return app.sources(App.class)
            }
        }
        

        gradle build 執行的 task 很多,這裡放下輸出

        下午4:26:33: Executing task 'build'...
        
        > Task :compileJava
        > Task :processResources NO-SOURCE
        > Task :classes
        > Task :bootWar
        > Task :bootStartScripts
        > Task :bootDistTar
        > Task :bootDistZip
        > Task :jar SKIPPED
        > Task :startScripts
        > Task :distTar
        > Task :distZip
        > Task :war SKIPPED
        > Task :assemble
        > Task :compileTestJava
        > Task :processTestResources NO-SOURCE
        > Task :testClasses
        > Task :test
        > Task :check
        > Task :build
        
        Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
        Use '--warning-mode all' to show the individual deprecation warnings.
        See //docs.gradle.org/6.4.1/userguide/command_line_interface.html#sec:command_line_warnings
        
        BUILD SUCCESSFUL in 2s
        10 actionable tasks: 10 executed
        下午4:26:35: Task execution finished 'build'.
        

SpringBoot提供的tasks

  • bootWar: 編譯並打成war包(需要依賴war插件才會有這個task),內部先調用classes,再調用自身,兩個 task

    下午4:14:07: Executing task 'bootWar'...
    
    > Task :compileJava
    > Task :processResources NO-SOURCE
    > Task :classes
    > Task :bootWar
    
    BUILD SUCCESSFUL in 510ms
    2 actionable tasks: 2 executed
    下午4:14:08: Task execution finished 'bootWar'.
    
  • bootJar: 編譯並打成jar包,內部先調用classes,再調用自身,兩個 task

    下午4:15:31: Executing task 'bootJar'...
    
    > Task :compileJava
    > Task :processResources NO-SOURCE
    > Task :classes
    > Task :bootJar
    
    BUILD SUCCESSFUL in 488ms
    2 actionable tasks: 2 executed
    下午4:15:32: Task execution finished 'bootJar'.
    

05.依賴管理

導入依賴

  • compile:從倉庫里下載並編譯,支援依賴傳遞

  • api:新語法,等同compile

  • implementation:新語法,與api相比不支援傳遞依賴,減少循環編譯優化效率

    compile/api/implementation導入的依賴都是編譯期與運行期都會提供的(打進位品中)

擴展閱讀:

implementation作用域

implementation的「訪問隔離」只作用在編譯期。

什麼意思呢?如果lib C 依賴了lib A 2.0版本,lib B implementation依賴了lib A 1.0版本:

那麼編譯期,libC 可訪問2.0版本的libA,libB可訪問1.0版本的libA。

但最終打到war中的是2.0版本(通過依賴樹可看到)。

在運行期,lib B 和lib C都可訪問lib A的2.0版本(只能用war中的lib)

implementation的特點有哪些?

對於使用了該命令編譯的依賴,對該項目有依賴的項目將無法訪問到使用該命令編譯的依賴中的任何程式,也就是將該依賴隱藏在內部,而不對外部公開

使用implementation有什麼好處?

如果項目中有很多級聯的工程依賴,比如上圖中lib A B C的依賴是工程依賴。如果使用的依賴方式是compile/api,那麼當lib A介面修改後重新編譯時,會重新編譯libA B C(即使lib C中並沒有用到修改的libA的介面)。如果使用implementation依賴,因為「編譯期隔離」的原因,不直接相關的lib就不會進行重新編譯

如果項目中都是aar依賴,編譯減少時長這個優點就沒有了(因為aar已經是編譯好的位元組碼了)。那麼還有什麼用呢?還是以上圖為例。之前我們都是compile依賴,如果lib A已經依賴了lib B,那麼在libC的build.gradle中就不用寫lib A的依賴了。但這樣會有問題:

我從lib C的build.gradle的依賴列表中不能完整的知道libC都需要依賴哪些lib。
假設這麼一種情況,我知道項目中的依賴的libA的最高版本是2.0,那麼app運行時就是使用的這個2.0版本的libA。這時候我需要打一個libC的aar。lib C如果通過compile傳遞依賴了libA,因此從lib C的build.gradle中不知道lib C 編譯時依賴的是哪個版本的lib A。如果libC 打aar(編譯)時,依賴的仍然libA 1.0,可能這個aar就有問題了。

屏蔽依賴

  • providedCompile:編譯期參與編譯,運行期不提供(但生成的war包中,會將這些依賴打入WEB-INF/lib-provided中)
  • providedRuntime:不參與編譯但運行期需要,比如 mysql 驅動包,如果在程式碼里用到了此依賴則編譯失敗

測試期依賴

  • testCompile:測試期編譯,非測試期不編譯
  • testImplementation:與implementation相同,僅是測試周期的

排除依賴

  • exclude:排除指定 group module的模組

    • 引入依賴時屏蔽

      dependencies {
          compile ('org.springframework.boot:spring-boot-starter-web'){
            exclude group:'org.springframework.boot',module:'spring-boot-starter-logging'
          }
      }
      
    • 全局屏蔽

      configurations.all {
      	exclude group:'org.springframework.boot', module:'spring-boot-starter-logging'
      }
      

      configurations {
       all*.exclude group:'org.springframework.boot',module:'spring-boot-starter-logging'
      }
      

依賴管理

  • dependencyManagement:統一多模組依賴版本

    • 可以在裡邊定義 dependencies 塊,指定依賴版本

      dependencyManagement {
          dependencies {
              api 'mysql:mysql-connector-java:5.1.39'
          }
      }
      
    • 可以引入BOM,類似引入Maven的parent

      dependencyManagement {
          imports {
              mavenBom 'io.spring.platform:platform-bom:1.1.1.RELEASE'
          }
      }
      

06.更多

參詳官方文檔:

07.引文