2021-12-29

Gradle

M_Gradle 官网 - 发布下载页面 官网 - Gradle User Manual 官网 - 核心插件 官网 - Kotlin - DLS API

Gradle is an open-source build automation tool focused on flexibility(专注灵活性) and performance.

projects 和 tasks

W3C Projects 和 tasks

projects 和 tasks是 Gradle 中最重要的两个概念;

projects 项目

任何一个 Gradle 构建都是由一个或多个 projects 组成; 每个 project 包括许多可构建组成部分; 这完全取决于你要构建些什么; 举个例子, 每个 project 或许是一个 jar 包或者一个 web 应用, 它也可以是一个由许多其他项目中产生的 jar 构成的 zip 压缩包;

一个project代表一个正在构建的组件(Jar/war文件),当构建开始时,Gradle会基于build.gradle实例化一个 org.gradle.api.Project 对象,并通过project变量来隐式调用其成员。 将build.gradle配置封装为一个Project对象,对象名字为 project,通过 project 可以隐式调用:默认使用groovy 语法

tasks 任务

每个 project 都由多个 tasks 组成; 每个 task 都代表了构建执行过程中的一个原子性操作; 如编译, 打包, 生成 javadoc, 发布到某个仓库等操作;

Gradle 项目实例

Gradle 目录说明

https://docs.gradle.org/current/userguide/partr1_gradle_init.html

.
├── gradle                        1      
│   └── wrapper
├── gradlew                       2      
├── gradlew.bat                   2      
├── settings.gradle.kts           3      
└── app
    ├── build.gradle.kts          4      
    └── src
        ├── main
        │   └── java              5      
        │       └── demo
        │           └── App.java
        └── test
            └── java              6      
                └── demo
                    └── AppTest.java
  1. Generated folder for wrapper files
  2. Gradle wrapper start scripts (启动脚本)
  3. Settings file to define build name and subprojects
  4. Build script for app subproject
  5. Default Java source folder for app subproject
  6. Default Java test source folder for app subproject

settings.gradle.kts 配置文件

rootProject.name = "authoring-tutorial"
include("app")
include("subject01")
include("subject02")
...

rootProject.name 为构建分配一个名称 include(“app”)定义构建由一个名为app的子项目组成,该子项目包含自己的源代码和构建逻辑。

build.gradle.kts 配置文件

每个项目,对应一个 build.gradle 的构建脚本

plugins {         1
    id("application")                                               
}
 
repositories {    2
    mavenCentral()                                                  
}
 
dependencies {    
    testImplementation("org.junit.jupiter:junit-jupiter:5.9.3")      3
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
    implementation("com.google.guava:guava:32.1.1-jre")              4
}
 
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)                 5
    }
}
 
application {
    mainClass = "org.example.App"         6                          
}
 
tasks.named<Test>("test") {
    useJUnitPlatform()                    7                          
}
 
 
  1. 用于引入 Gradle 插件, 对用Java构建CLI应用程序的支持。
  2. 使用 Maven Central解决依赖关系。
  3. 测试代码编译和运行时依赖。
  4. 声明项目依赖。
  5. 定义工具链版本。
  6. 定义应用程序的主类。
  7. 使用JUnit平台进行单元测试

插件 plugin

用于引入 Gradle 插件,类似于 Maven 的 <packaging> 和插件配置。

  • id:插件 ID,内置插件(如 java)无需版本号。
  • version:第三方插件版本号(可选)。
  • apply false:声明插件但不立即应用(用于多模块项目)。

仓库配置 (repositories)

声明依赖仓库,类似于 Maven 的 <repositories>

常用仓库

  • mavenCentral():Maven 中央仓库。
  • mavenLocal():本地 Maven 仓库。
  • google():Google 的 Maven 仓库。
repositories {
	// 改为阿里云的镜像地址
	maven { setUrl("https://maven.aliyun.com/repository/central") }
	maven { setUrl("https://maven.aliyun.com/repository/jcenter") }
	maven { setUrl("https://maven.aliyun.com/repository/google") }
	maven { setUrl("https://maven.aliyun.com/repository/gradle-plugin") }
	maven { setUrl("https://maven.aliyun.com/repository/public") }
	mavenCentral()
	gradlePluginPortal()
}

依赖配置 (dependencies)

dependencies {
    //依赖当前项目下的某个模块[子工程] 
    implementation project(':subject01')
    //直接依赖本地的某个jar文件 
    implementation files('libs/foo.jar', 'libs/bar.jar') 
    //implementation(fileTree("D:\\library\\javafx-sdk-24.0.1\\lib")) // 自动包含目录下所有 JAR
    //配置某文件夹作为依赖项 
    implementation fileTree(dir: 'libs', include: ['*.jar']) 
    //直接依赖 
    implementation 'org.apache.logging.log4j:log4j:2.17.2' 
}
  • implementation:主代码编译和运行时依赖。
  • testImplementation:测试代码编译和运行时依赖。
  • compileOnly:仅编译时依赖(类似 Maven 的 provided
  • runtimeOnly:仅运行时依赖(类似 Maven 的 runtime

扩展配置 (extensions)

配置插件提供的扩展属性

java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(21)) // 指定 JDK 版本
    }
}
  • java:Java 插件提供的扩展。
  • kotlin:Kotlin 插件提供的扩展。

附 作用域配置对象表格

​名称/方法​​类型/接口​​作用​​示例​​常用方法/配置​
buildscriptScriptHandler配置构建脚本自身的依赖(如插件、工具)groovy<br>buildscript {<br> repositories { mavenCentral() }<br>}repositories {}dependencies {}
pluginsPluginDependenciesSpec声明或应用插件groovy<br>plugins {<br> id 'java'<br>}id()version()apply()
allprojects方法(接收闭包)为​​所有项目​​(包括根项目)配置公共属性groovy<br>allprojects {<br> version = '1.0.0'<br>}闭包内可配置任意 Project 属性或任务
subprojects方法(接收闭包)仅配置​​子项目​​(排除根项目)groovy<br>subprojects {<br> apply plugin: 'java'<br>}闭包内配置子项目属性或任务
repositoriesRepositoryHandler配置依赖仓库地址groovy<br>repositories {<br> mavenCentral()<br>}mavenCentral()google()jcenter()
dependenciesDependencyHandler添加项目依赖(如第三方库)groovy<br>dependencies {<br> implementation 'com.example:lib:1.0'<br>}implementation()testImplementation()
configurationsConfigurationContainer管理自定义依赖配置groovy<br>configurations {<br> customConfig<br>}定义新配置(如 customConfig
artifactsArtifactHandler定义发布的构件(需配合插件使用)groovy<br>artifacts {<br> archives file('libs/example.jar')<br>}archives()file()
tasksTaskContainer管理项目中的任务(创建、配置任务)groovy<br>tasks.register('hello') {<br> doLast { println 'Hello!' }<br>}register()create()configure()
​Project 属性​Project 对象属性直接配置根项目的属性groovy<br>version = '1.0.0'<br>group = 'com.example'versiongroupdescription
ext扩展属性定义全局共享的自定义属性groovy<br>ext {<br> kotlinVersion = '1.8.0'<br>}通过 ext {} 块定义属性

gradle.properties 配置文件

Gradle properties

配置 Gradle 运行的JDK

Gradle 提供了几个选项,可以轻松地配置将用于执行构建的Java进程。

可以通过GRADLE_OPTS或JAVA_OPTS在本地环境中配置这些设置,但能够在版本控制中存储某些设置(如JVM内存配置和JAVA_HOME位置)是很有用的,这样整个团队就可以在一致的环境中工作。

另外在本地仓库根目录的 F:\gradle_local_repository\gradle.properties 亦有效的!!

要执行此操作,请将这些设置放入grade.properties文件中

配置代理

1、全局gradle使用代理:{userdir}/.gradle/gradle.properties

在gradle.properties加入如下内容: gradle.properties

systemProp.http.proxyHost=127.0.0.1
systemProp.http.proxyPort=10808
systemProp.https.proxyHost=127.0.0.1
systemProp.https.proxyPort=10808
# 对于国内的仓库可以不走代理,还有部分内网地址也可以不走代理 
systemProp.http.nonProxyHosts=developer.huawei.com|maven.aliyun.com|192.168.*

buildSrc

运行 Gradle 时会检查项目中是否存在一个名为 buildSrc 的目录。然后 Gradle 会自动编译并测试这段代码,并将其放入构建脚本的类路径中, 对于多项目构建,只能有一个 buildSrc 目录,该目录必须位于根项目目录中, buildSrc 是 Gradle 项目根目录下的一个目录,它可以包含我们的构建逻辑,与脚本插件相比,buildSrc 应该是首选,因为它更易于维护、重构和测试代码

依赖冲突

假设你的项目依赖于一个库,而这个库又依赖于其他库。你不必自己去找出所有这些依赖,你只需要加上你直接依赖的库,Gradle会隐式的把这些库间接依赖的库也加入到你的项目中。

(1)依赖传递性:

假设你的项目依赖于一个库,而这个库又依赖于其他库。你不必自己去找出所有这些依赖,你只需要加上你直接依赖的库,Gradle会隐式的把这些库间接依赖的库也加入到你的项目中。

image.png

(2)传递性依赖中版本冲突:

由于传递性依赖的特点,两个不同版本的jar包会被依赖进来,这样就存在版本冲突的问题。

image.png

maven中解决冲突的办法

【1】第一原则:最短路径优先原则

“最短路径优先”意味着项目依赖关系树中路径最短的版本会被使用。

例如,假设A、B、C之间的依赖关系是ABCD(2.0) 和AE(D1.0),那么D(1.0)会被使用,因为A通过E到D的路径更短。

【2】第二原则:最先声明原则

依赖路径长度是一样的的时候,第一原则不能解决所有问题,比如这样的依赖关系:A–>B–>Y(1.0),A–>C–>Y(2.0),Y(1.0)和Y(2.0)的依赖路径长度是一样的,都为2。那么到底谁会被解析使用呢?在maven2.0.8及之前的版本中,这是不确定的,但是maven2.0.9开始,为了尽可能避免构建的不确定性,maven定义了依赖调解的第二原则:第一声明者优先。在依赖路径长度相等的前提下,在POM中依赖声明的顺序决定了谁会被解析使用。顺序最靠前的那个依赖优胜。

Gradle中解决冲突的办法

(4)Gradle中解决冲突的办法-自动解决方案:

Gradle的默认自动解决版本冲突的方案是选用版本最高的。

案例:加入两个依赖:

implementation group: 'org.springframework', name: 'spring-jdbc', version: '5.1.3.RELEASE'
implementation group: 'org.springframework', name: 'spring-webmvc', version: '5.1.13.RELEASE'
 

image.png

(5)Gradle中解决冲突的办法-手动修改依赖:

手动排除依赖:

dependencies { 
    implementation(group: 'org.springframework', name: 'spring-jdbc', version: '5.1.3.RELEASE'){
        exclude group:'org.springframework',module:'spring-beans'
    }
}

项目打包发布

Spring Boot 项目打包 jar & lib依赖分离

  1. 禁用bootJar任务,启用jar任务。
  2. 配置jar任务的manifest,设置Main-Class为应用程序的主类(例如com.example.ApplicationKt),而不是Spring Boot的Launcher。
  3. 在manifest中设置Class-Path为lib/下的所有依赖jar。
  4. 创建一个任务将runtimeClasspath的依赖复制到build/libs/lib目录。
 
  
tasks.register<Copy>("copyJar")  
  
// 复制jar依赖包  
tasks.named<Copy>("copyJar") {
	//buildDir 属性已被弃用 使用 layout
    delete("${layout.buildDirectory.asFile.get()}/libs/lib")  
    from(configurations.runtimeClasspath).into("${layout.buildDirectory.asFile.get()}/libs/lib")  
}  
  
// 打包时排除其他依赖jar  
tasks.named<org.springframework.boot.gradle.tasks.bundling.BootJar>("bootJar") {//修改 bootJar 打包行为
    setExcludes(listOf("*.jar"))  
    dependsOn("copyJar")  
    manifest {  
        // 指定Class-Path, 可以在运行时不用指定 loader.path  参数
        attributes(  
            "Class-Path" to configurations.runtimeClasspath.get().files.map { "lib/${it.name}" }.joinToString(" ")  
        )  
    }  
}
  1. 执行命令
./gradlew build

build 构建完成后,build/libs 目录将包含:

  • 主 jar 文件(如 app.jar
  • lib 目录(包含所有依赖的 jar)

Kotlin Application 项目打包 jar & lib依赖分离

plugins {
    id("java")
    id("org.jetbrains.kotlin.jvm") version "2.2.20"
}
 
 
dependencies {
	.................
}
// 1. 配置 `jar`任务
tasks.jar {
    manifest {
        attributes(
            "Main-Class" to "org.yang.pdf.classify.MainKt", // 替换为你的主类
            "Class-Path" to configurations.runtimeClasspath.get()
                .files.joinToString(" ") { "lib/${it.name}" }
        )
    }
}
 
val V_BuildDir = layout.buildDirectory.asFile.get()
// 2. 复制依赖任务
tasks.register<Copy>("copyDependencies") {
    from(configurations.runtimeClasspath)
    into("${V_BuildDir}/libs/lib")
}
// 3. 完整打包任务
tasks.register<Zip>("buildDist") {
    dependsOn("jar", "copyDependencies")
    archiveFileName.set("${project.name}-dist.zip")
    destinationDirectory.set(V_BuildDir.resolve("dist"))
    from(V_BuildDir.resolve("libs")) {
        include("*.jar")
        include("lib/**")
    }
}
 
 

镜像源

https://developer.aliyun.com/mvn/guide

for java

Gradle 不支持镜像源的直接设置,只能通过 maven() 方法设置一个新的 Maven 仓库地址。

在项目中的 build.gradle(Project)修改内容

buildscript {
    repositories {
         maven {
            url 'https://maven.aliyun.com/repository/public/'
        }
        maven {
            url 'https://maven.aliyun.com/repository/google/'
        }
    }
    dependencies {
 
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }        
}
 
allprojects {
    repositories {
        maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
        maven{ url 'http://maven.aliyun.com/nexus/content/repositories/jcenter'}
    }
}

另外又加了个 jcenter 仓库, 下载也是慢(jcenter是一个由 bintray.com维护的Maven仓库)

buildscript {
    ext.anko_version = '0.10.8'
    ext.kotlin_version = '1.3.72'
    ext.retrofit_version = '2.4.0'
    repositories {
        jcenter()

将 jcenter() 改为mavenCentral(), 或者删掉

for kotlin

在 gradle.kts 中配置 settings.gradle.kts

pluginManagement {
    repositories {
        // 改为阿里云的镜像地址
        maven { setUrl("https://maven.aliyun.com/repository/central") }
        maven { setUrl("https://maven.aliyun.com/repository/jcenter") }
        maven { setUrl("https://maven.aliyun.com/repository/google") }
        maven { setUrl("https://maven.aliyun.com/repository/gradle-plugin") }
        maven { setUrl("https://maven.aliyun.com/repository/public") }
        mavenCentral()
        gradlePluginPortal()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        // 改为阿里云的镜像地址
        maven { setUrl("https://maven.aliyun.com/repository/central") }
        maven { setUrl("https://maven.aliyun.com/repository/jcenter") }
        maven { setUrl("https://maven.aliyun.com/repository/google") }
        maven { setUrl("https://maven.aliyun.com/repository/gradle-plugin") }
        maven { setUrl("https://maven.aliyun.com/repository/public") }
        mavenCentral()
    }
}
 
rootProject.name = "My Application"
include(":app")
 

修改

file/C:\\Users\\yang\\Downloads\\gradle-8.4-bin.zip

复用 maven 本地仓库资源

在本地开发环境中已经有一个Maven仓库, Gradle能够使用这些本地的依赖, 而不是重新从远程仓库下载。

repositories {
	mavenLocal{setUrl("file:///F:\\gradle_local_repository")}  // 优先从本地仓库查找依赖 
    maven { setUrl("https://maven.aliyun.com/repository/public") } // 镜像源
    mavenCentral()// 最后查找中央仓库
}