使用gradle但不使用java插件构建java项目

Java团长      2022-05-09     592

关键词:

本文目标是探索在没有使用任何额外插件的情况下,如何使用 Gradle 构建一个 Java 项目,以此对比使用 Java 插件时得到的好处。

初始化项目

使用 Gradle Init 插件提供的 init task 来创建一个 Gradle 项目:

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

运行完成后,我们将得到这些文件:

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

接下来,我们将关注点放到 build.gradle 上面,这是接下来编写构建脚本的地方。

Hello World

首先,我们编写一个 Java 的 HelloWorld,做为业务代码的代表:

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

然后,将这个内容保存到 src/HelloWorld.java 文件中,不按照 maven 的约定来组织项目结构。

编译 Java

接着,我们需要给我们的构建脚本添加任务来编译刚才写的 Java 文件。这里就需要使用到 Task 。关于 Task , Gradle 上有比较详细的文档描述如何使用它: https://docs.gradle.org/curre... & https://docs.gradle.org/curre... 。

现在,我们可以创建一个 JavaCompile 类型的 Task 对象,命名为 compileJava :

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

在上面的代码中,我们:

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

因为 mavenCentral 和 jcenter 是比较常见的两个仓库,所以 Gradle 提供了函数可以直接使用。而其他的仓库则需要自己指定仓库的地址。

申明了 repository 之后, Gradle 才会知道在哪里寻找申明的依赖。

定义 configuration

如果你使用过 maven 的话,也许 repository 和 dependency 都能理解,但对 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 这个 task 的 classpath 中,这样我们在代码中使用的 guava 提供的代码就能在编译期被 JVM 识别到。

但就像在中描述的那样,我们需要消费 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 的输出中拷贝文件,这样就隐式的添加了 jar 对 compileJava 的依赖
  3. 使用 include 要求仅复制 class 文件
  4. 可以使用 manifest 给 META-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 以区别 fatJar 和 jar 产生的不同 jar 文件
  2. 使用 from 将 forHelloWorld 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 提供的一小点功能,伤害太明显。

配置现有的 eclipse java 项目以使用 gradle 构建

】配置现有的eclipsejava项目以使用gradle构建【英文标题】:configuringexistingeclipsejavaprojecttobuildusinggradle【发布时间】:2013-01-1423:15:55【问题描述】:我在Eclipse中有一个现有的Java项目。我想使用gradle实现构建。我尝试使用给定here的... 查看详情

androidgradle插件gradle构建工具简介③(gradle构建脚本编程语言|groovy语言简介|groovy语言特性)

...一、Gradle构建脚本编程语言Gradle构建工具的构建脚本可以使用Groovy语言或Kotlin语言进行编写,使用Groovy语言编写的构建脚本是build.gradle构建脚本;当前大量的Android应用,都是使用Groovy语言进行配置,因此老的项目维护时,需要用到该语... 查看详情

gradle梳理-插件

...orial-part-2-java-projects/在这部分的教学中,我们会学习如何使用Gradle去编译,构建,测试你的JAVA项目。我们将会把关注的焦点放在Gradle的内部机制上,而不是java代码,所以我将使用非常简单的java工程来帮助我们理解Gradle的概念。... 查看详情

gradle插件(代码片段)

...有内置或者有些功能没有提供,我们也可以自定义插件来使用,例如AndroidGradle插件就是基于Java插件扩展的。插件的作用插件可以封装一系列任务,例如编译,测试,打包等。插进可以定义约定,例如应用Java插件后,约定的源代... 查看详情

android自定义gradle插件(代码片段)

一、Gradle我们知道在我们现在使用AndroidStduio开发Android项目的时候,AndroidStudio是基于Gradle来帮助我们构建,管理项目的。Gradle:Gradle是一个项目构建工具,用来帮助我们管理项目的依赖、打包、发布、部署等工作。Gradl... 查看详情

使用 gradle 构建开源依赖项

】使用gradle构建开源依赖项【英文标题】:Buildingopensourcedependenciesusinggradle【发布时间】:2021-12-1022:02:39【问题描述】:我真的没有太多开发经验,更不用说使用构建工具了。我被分配了一项任务来在本地构建依赖项并获取jar文... 查看详情

使用gradle构建一个非常简单的java程序

】使用gradle构建一个非常简单的java程序【英文标题】:Usinggradletobuildaverysimplejavaprogram【发布时间】:2018-05-2300:46:22【问题描述】:我是Java和Gradle的新手,并且有一个非常新手的问题。我有以下Java文件:publicclassTestMainpublicstaticv... 查看详情

使用 Gradle 构建 JavaFX 应用程序

】使用Gradle构建JavaFX应用程序【英文标题】:BuildingaJavaFXapplicationusingGradle【发布时间】:2015-10-0206:13:38【问题描述】:我正在尝试使用Gradle构建一个相对简单的JavaFX应用程序。但是,我不知道该怎么做。我对Gradle比较陌生,对... 查看详情

Android/Gradle/Unity - 在 AAR 插件中包含所有依赖项

】Android/Gradle/Unity-在AAR插件中包含所有依赖项【英文标题】:Android/Gradle/Unity-includealldependenciesinAARplugin【发布时间】:2015-10-0400:03:51【问题描述】:我想为Unity构建一个插件来包装最新的GoogleCloudMessagingAPI(GCM)。我编写了Java代码... 查看详情

查找未使用的gradle运行时依赖项(代码片段)

我正在更新大量遗留Java应用程序。当前版本使用ant及其依赖项来自每个项目的lib目录。依赖项是每个项目的一部分,并签入源代码控制。更新的目的是将构建转换为gradle并使用maven存储库进行依赖关系管理。为简单起见,遗留构... 查看详情

使用 gradle 的多项目测试依赖项

】使用gradle的多项目测试依赖项【英文标题】:Multi-projecttestdependencieswithgradle【发布时间】:2011-08-0409:07:44【问题描述】:我有一个多项目配置,我想使用gradle。我的项目是这样的:项目A->src/main/java->src/test/java项目B->src/m... 查看详情

androidgradle插件gradle自动化构建①(gradle构建工具简介|gradle构建工具用途)

...建工具用途一、Gradle构建工具简介在最开始Android开发时,使用Eclipse+ADT进行开发,使用的是Ant构建工具进行构建;常用的构建工具有AntMavenGradle在Android开发中,Gradle构建工具是必须要精通的;Gradle是基于Ant和Maven的自动化构建工具,该... 查看详情

gradle+groovy基础篇(代码片段)

...dle,几乎是寸步难行的。除了没有复杂的XML以外,Gradle还使用Groovy或Kotlin编写的构建脚本提供了灵活性和更快的构建速度。借助Kotlin或Groovy的全部功能以及GradleAPI库,您可以创建功能强大且复杂的构建脚本。这肯定是提升效率的... 查看详情

教小白使用gradle构建java项目

本指南将引导您逐步使用Gradle构建一个简单的Java项目。你会建立什么您将创建一个简单的应用程序,然后使用Gradle进行构建。你需要什么1.约15分钟2.最喜欢的文本编辑器或IDE3.JDK6或更高版本如何完成本指南像大多数Spring入门指... 查看详情

gradle(代码片段)

概述类似ant和maven,简单来说是一个构建工具,高级使用的话也可以作为一个基于Groovy语言的编程框架,所有配置其实都是code。参考SpringBootGradlePluginReferenceGuideGradle史上最详细解析十分钟理解Gradle基于Gradle的SpringBoot项目构建用Gr... 查看详情

intellij 构建包含 gradle 依赖项的 jar 工件

...为它应该很简单。我的目标是创建一个Intellijgradle项目,使用gradle向模块添加一些依赖项,并向其中添加一些java源代码。然后我只想有一个选项以 查看详情

使用 maven 的 pom.xml 来构建 gradle 依赖项?

】使用maven的pom.xml来构建gradle依赖项?【英文标题】:usemaven\'spom.xmlforgradlebuilddependencies?【发布时间】:2018-01-0208:51:58【问题描述】:我的问题很简单。我有一个mavenjava项目,我想用gradle构建它。目前我有一个pom.xml文件,我在... 查看详情

Gradle:使用 Spring Boot 依赖项构建“fat jar”

】Gradle:使用SpringBoot依赖项构建“fatjar”【英文标题】:Gradle:Build\'fatjar\'withSpringBootDependencies【发布时间】:2016-02-0806:58:52【问题描述】:我正在使用Gradle构建一个非常简单的SpringBoot应用程序(99%静态内容)并将其打包到一个... 查看详情