原文地址: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
首先,咱们编写一个 Java
的 HelloWorld,作为业务代码的表明:shell
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello Wrold"); } }
而后,将这个内容保存到 src/HelloWorld.java
文件中,不按照 maven
的约定来组织项目结构。api
接着,咱们须要给咱们的构建脚本添加任务来编译刚才写的 Java
文件。这里就须要使用到 Task
。关于 Task
,Gradle
上有比较详细的文档描述如何使用它:https://docs.gradle.org/curre... & https://docs.gradle.org/curre...。bash
如今,咱们能够建立一个 JavaCompile
类型的 Task
对象,命名为 compileJava
:app
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
source
& include
方法指定了要被编译的文件所在的目录和文件的扩展名destinationDir
指定了编译后的 class
文件的存放目录sourceCompatibility
& targetCompatibility
指定了源码的 Java
版本和 class
文件的版本classpath
指定了编译时使用的 classpath
那么,接下来咱们就能够执行 compileJava
这个任务了:
❯ gradle compileJava ❯ tree build build ├── classes │ └── HelloWorld.class └── tmp └── compileJava ❯ cd build/classes ❯ java HelloWorld Hello World
咱们能够看到,HelloWorld 已经编译成功,而且能够被正确执行。
在实际的项目中,不免会使用到其余人开发的库。要使用别人开发的库,就须要添加依赖。在 Gradle
中添加依赖,须要作这样四个事情:
repository
configuration
dependency
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
中描述的那样,咱们须要消费 configuration
以达到使用依赖的目的,而不能直接使用依赖。因此咱们须要将 compileJava.classpath
修改为下面这样:
classpath = files("${buildDir}/classes", configurations.forHelloWorld)
完成上面四步以后,咱们就能够在咱们的代码中使用 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
:
Jar
,里面不包含本身的依赖,而是在 Jar
文件外的一个 metadata
文件申明依赖,好比 maven
中的 pom.xml
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") }
在这个例子中,咱们:
archiveBaseName
, archiveAppendix
, archiveVersion
属性,他们和 archiveClassfier
, archiveExtension
将决定最后打包好的 jar
文件名from
方法,指定要从 compileJava
的输出中拷贝文件,这样就隐式的添加了 jar
对 compileJava
的依赖include
要求仅复制 class
文件manifest
给 META-INF/MANIFEST.MF
文件添加信息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
,咱们的配置变动在于:
archiveClassfier
以区别 fatJar
和 jar
产生的不一样 jar
文件from
将 forHelloWorld
configuration
的依赖所有解压后拷贝到 jar
文件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
提供的一小点功能,伤害太明显。
完整的例子能够在这个 repo
的 commit
中找到 https://github.com/kbyyd24/gr...