使用 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)
相关文章
相关标签/搜索