使用更快的仓库
- 阿里云官方给的文档
- 腾讯云软件源加速软件包下载和更新
gradle 配置阿里云的代理仓库
如果使用 gradle 管理java项目,可以在 build.gradle 中加入:
allprojects {
repositories {
maven { url 'https://maven.aliyun.com/repository/public/' }
mavenLocal()
mavenCentral()
}
}
gradle 配置腾讯云的代理仓库
类似的,配置如下:
allprojects {
repositories {
maven { url 'http://mirrors.cloud.tencent.com/nexus/repository/maven-public/' }
mavenLocal()
mavenCentral()
}
}
Java 插件
在当前目录中创建 build.gradle ,内容如下:
apply plugin: 'java'
或者写成:
plugins {
id 'java'
}
执行下面的命令查看有哪些任务:
$ gradle tasks
> Task :tasks
------------------------------------------------------------
Tasks runnable from root project
------------------------------------------------------------
Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the main classes.
testClasses - Assembles test classes.
Build Setup tasks
-----------------
init - Initializes a new Gradle build.
wrapper - Generates Gradle wrapper files.
Documentation tasks
-------------------
javadoc - Generates Javadoc API documentation for the main source code.
Help tasks
----------
buildEnvironment - Displays all buildscript dependencies declared in root project 'gradle-demo-0004'.
components - Displays the components produced by root project 'gradle-demo-0004'. [incubating]
dependencies - Displays all dependencies declared in root project 'gradle-demo-0004'.
dependencyInsight - Displays the insight into a specific dependency in root project 'gradle-demo-0004'.
dependentComponents - Displays the dependent components of components in root project 'gradle-demo-0004'. [incubating]
help - Displays a help message.
model - Displays the configuration model of root project 'gradle-demo-0004'. [incubating]
projects - Displays the sub-projects of root project 'gradle-demo-0004'.
properties - Displays the properties of root project 'gradle-demo-0004'.
tasks - Displays the tasks runnable from root project 'gradle-demo-0004'.
Verification tasks
------------------
check - Runs all checks.
test - Runs the unit tests.
Rules
-----
Pattern: clean<TaskName>: Cleans the output files of a task.
Pattern: build<ConfigurationName>: Assembles the artifacts of a configuration.
Pattern: upload<ConfigurationName>: Assembles and uploads the artifacts belonging to a configuration.
To see all tasks and more detail, run gradle tasks --all
To see more detail about a task, run gradle help --task <task>
BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed
使用 init 初始化 Java 项目
创建 Java Application 项目
执行 gradle init --type java-application
,根据提示输入内容即可。
▶ gradle init --type java-application
Select build script DSL:
1: groovy
2: kotlin
Enter selection (default: groovy) [1..2] 1
Select test framework:
1: junit
2: testng
3: spock
Enter selection (default: junit) [1..3] 1
Project name (default: gradle-demo-0004): gradle-demo
Source package (default: gradle.demo): demo
BUILD SUCCESSFUL in 33s
2 actionable tasks: 2 executed
创建完成后,目录结构如下:
▶ tree .
.
├── build.gradle
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
├── main
│ ├── java
│ │ └── demo
│ │ └── App.java
│ └── resources
└── test
├── java
│ └── demo
│ └── AppTest.java
└── resources
setting.gradle 内容如下:
rootProject.name = 'gradle-demo-0004'
build.gradle 内容如下:
plugins {
// Apply the java plugin to add support for Java
id 'java'
// Apply the application plugin to add support for building an application
id 'application'
}
repositories {
// Use jcenter for resolving your dependencies.
// You can declare any Maven/Ivy/file repository here.
jcenter()
}
dependencies {
// This dependency is found on compile classpath of this component and consumers.
implementation 'com.google.guava:guava:27.0.1-jre'
// Use JUnit test framework
testImplementation 'junit:junit:4.12'
}
// Define the main class for the application
mainClassName = 'demo.App'
创建 Java Library 项目
▶ gradle init --type java-library
Select build script DSL:
1: groovy
2: kotlin
Enter selection (default: groovy) [1..2] 1
Select test framework:
1: junit
2: testng
3: spock
Enter selection (default: junit) [1..3] 1
Project name (default: gradle-demo-0004): gradle-demo
Source package (default: gradle.demo): demo
BUILD SUCCESSFUL in 19s
2 actionable tasks: 2 executed
▶ tree .
.
├── build.gradle
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
├── main
│ ├── java
│ │ └── demo
│ │ └── Library.java
│ └── resources
└── test
├── java
│ └── demo
│ └── LibraryTest.java
└── resources
setting.gradle
内容如下:
rootProject.name = 'gradle-demo'
build.gradle
内容如下:
plugins {
// Apply the java-library plugin to add support for Java Library
id 'java-library'
}
repositories {
// Use jcenter for resolving your dependencies.
// You can declare any Maven/Ivy/file repository here.
jcenter()
}
dependencies {
// This dependency is exported to consumers, that is to say found on their compile classpath.
api 'org.apache.commons:commons-math3:3.6.1'
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
implementation 'com.google.guava:guava:27.0.1-jre'
// Use JUnit test framework
testImplementation 'junit:junit:4.12'
}
创建含有多模块(即多个子项目)的项目
示例1:各子项目单独配置
项目结构:
.
├── child1
│ ├── build.gradle
│ └── src
│ └── main
│ └── java
│ └── gradle
│ └── hello
│ └── child1
│ └── Calculate.java
├── child2
│ ├── build.gradle
│ └── src
│ └── main
│ └── java
│ └── gradle
│ └── hello
│ └── child2
│ └── CalculateService.java
├── build.gradle
└── settings.gradle
该示例中没有根项目,只有两个子项目child1
、child2
。
根目录文件
build.gradle 内容为空。
setting.gradle 内容如下:
rootProject.name = 'gradle-demo-0001'
include 'child1'
include 'child2'
child1/build.gradle 内容为:
group 'gradle.hello'
version '1.0-SNAPSHOT'
apply plugin: 'java'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
}
child1 项目
child1 中的 build.gradle 内容如下:
group 'gradle.hello'
version '1.0-SNAPSHOT'
apply plugin: 'java'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
}
child1 中的 Calculate 类内容为:
package gradle.hello.child1;
public class Calculate {
public static int add(int a, int b) {
return a+b;
}
}
child2 项目
child2 中的 build.gradle 类内容为:
group 'gradle.hello'
version '1.0-SNAPSHOT'
apply plugin: 'java'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile project(':child1') // 声明对 child1 项目的依赖
testCompile group: 'junit', name: 'junit', version: '4.12'
}
child2 中的 CalculateService 类内容为:
package gradle.hello.child2;
import gradle.hello.child1.Calculate; // 注意,这个是 child1 中的类
public class CalculateService {
public static void main(String[] args) {
int result = Calculate.add(1, 2);
System.out.println(result);
}
}
运行 CalculateService ,会输出 3。
查看所有任务(包括子项目的任务)
$ gradle tasks --all
示例2:集中配置子项目
项目结构:
.
├── child1
│ └── src
│ ├── main
│ ├── java
│ │ └── gradle
│ │ └── hello
│ │ └── child1
│ │ └── Calculate.java
│ └── resources
├── child2
│ └── src
│ ├── main
│ │ ├── java
│ │ │ └── gradle
│ │ │ └── hello
│ │ │ └── child2
│ │ │ └── CalculateService.java
│ │ └── resources
├── build.gradle
└── settings.gradle
结构和示例1类似,但是 child1 和 child2 中没有 build.gradle 文件。
根目录下的 settings.gradle 内容:
rootProject.name = 'gradle-demo-0002'
include 'child1'
include 'child2'
根目录下的 build.gradle 内容:
group 'gradle.hello'
version '1.0-SNAPSHOT'
apply plugin: 'java'
sourceCompatibility = 1.8
subprojects { // 给所有的子项目进行配置
apply plugin: 'java'
repositories {
mavenCentral()
}
}
// 配置 child1 子项目
project(":child1") {
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
}
}
// 配置 child2 子项目
project(":child2") {
dependencies {
compile project(":child1") // 声明对 child1 的依赖
testCompile group: 'junit', name: 'junit', version: '4.12'
}
}
执行 child2 下的 CalculateService ,会输出 3。
示例3:同时配置子项目和根项目
项目结构:
.
├── settings.gradle
├── build.gradle
├── child1
│ └── src
│ ├── main
│ ├── java
│ │ └── gradle
│ │ └── hello
│ │ └── child1
│ │ └── Calculate.java
│ └── resources
├── child2
│ └── src
│ ├── main
│ ├── java
│ │ └── gradle
│ │ └── hello
│ │ └── child2
│ │ └── CalculateService.java
│ └── resources
└── src
├── main
├── java
│ └── gradle
│ └── hello
│ └── Main.java
└── resources
结构和示例2类似,但是在根目录下增加了 src 目录,作为根项目的内容。
根项目下 src 目录中的 Main.java 内容如下:
package gradle.hello;
import gradle.hello.child1.Calculate; // 这是 child1 项目中的类
public class Main {
public static void main(String[] args) {
int result = Calculate.add(1, 2);
System.out.println(result);
}
}
根目录下 settings.gradle 内容如下:
rootProject.name = 'gradle-demo-0003'
include 'child1'
include 'child2'
根目录下 build.gradle 内容如下:
group 'gradle.hello'
version '1.0-SNAPSHOT'
apply plugin: 'java'
sourceCompatibility = 1.8
allprojects { // 给根项目和所有的子项目进行配置
apply plugin: 'java'
repositories {
mavenCentral()
}
}
project(":child1") { // 配置子项目 child1
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
}
}
project(":child2") { // 配置子项目 child2
dependencies {
compile project(":child1") // 依赖子项目 child1
testCompile group: 'junit', name: 'junit', version: '4.12'
}
}
// 配置根项目(rootProject)
dependencies {
compile project(":child1") // 依赖子项目 child1
testCompile group: 'junit', name: 'junit', version: '4.12'
}
运行 Main 类,会输出3。
plugins 和 apply plugin 的异同
我们有时会看到:
plugins {
id 'java'
}
有时会看到:
apply plugin: 'java'
这两个有什么异同吗 ?
plugins 和 apply plugin 的效果基本是一样的。
- plugins 是后来添加的特性。
- plugins 中指定的插件必须是 https://plugins.gradle.org/ 存在的。
- apply plugin 可以用在 allprojects 和 subprojects 中。
compile 和 testCompile 的区别
compile 和 testCompile 都是用于声明依赖,但适用范围不同。
例如一个项目的结构如下:
▶ tree .
.
├── build.gradle
├── settings.gradle
└── src
├── main
│ ├── java
│ │ └── demo
│ │ └── Calculate.java
│ └── resources
└── test
├── java
│ └── demo
│ └── CalculateTest.java
└── resources
其中,build.gradle
声明了如下依赖:
dependencies {
compile group: 'com.google.code.gson', name: 'gson', version: '2.8.5'
testCompile group: 'junit', name: 'junit', version: '4.12'
}
compile 用于声明整个项目的依赖,也就是src/main/java
和src/test/java
中都可以适用 compile 声明的依赖。
testCompile 声明的依赖只能用于测试代码,即 src/test/java
中的代码。
运行 Java 测试用例
我们用一个小项目来举例子。
项目结构如下:
▶ tree .
.
├── build.gradle
├── settings.gradle
└── src
├── main
│ ├── java
│ │ └── demo
│ │ └── Calculate.java
│ └── resources
└── test
├── java
│ └── demo
│ └── CalculateTest.java
└── resources
build.gradle
内容如下:
plugins {
id 'java'
}
group 'com.example'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
}
test {
// 这个配置,是为了让gradle在运行测试时将标准输出等展示出来
testLogging {
outputs.upToDateWhen {false}
showStandardStreams = true
}
}
src/main
目录下的 Calculate 类:
package demo;
public class Calculate {
public static int add(int a, int b) {
return a+b;
}
}
src/test
中的 CalculateTest 测试类:
package demo;
import org.junit.Assert;
import org.junit.Test;
public class CalculateTest {
@Test
public void testAdd01() {
System.out.println("测试 1+2");
Assert.assertEquals(3, Calculate.add(1, 2));
}
@Test
public void testAdd02() {
System.out.println("测试 2+2");
Assert.assertEquals(4, Calculate.add(2, 2));
}
}
运行整个项目的单测
使用 gradle 执行测试:
▶ gradle test
> Task :test
demo.CalculateTest > testAdd01 STANDARD_OUT
测试 1+2
demo.CalculateTest > testAdd02 STANDARD_OUT
测试 2+2
BUILD SUCCESSFUL in 2s
3 actionable tasks: 3 executed
注意,我们在 build.gradle 中配置了:
testLogging {
outputs.upToDateWhen {false}
showStandardStreams = true
}
如果不配置这段内容,gradle test
的结果是:
▶ gradle test
BUILD SUCCESSFUL in 1s
3 actionable tasks: 3 up-to-date
是的,没有什么详细内容。
运行一个测试类
▶ gradle test --tests demo.CalculateTest
> Task :test
demo.CalculateTest > testAdd01 STANDARD_OUT
测试 1+2
demo.CalculateTest > testAdd02 STANDARD_OUT
测试 2+2
BUILD SUCCESSFUL in 1s
3 actionable tasks: 1 executed, 2 up-to-date
运行单个测试
比如运行 CalculateTest 中的 testAdd01:
▶ gradle test --tests demo.CalculateTest.testAdd01
> Task :test
demo.CalculateTest > testAdd01 STANDARD_OUT
测试 1+2
BUILD SUCCESSFUL in 1s
3 actionable tasks: 1 executed, 2 up-to-date
Gradle 下载项目依赖
示例 1
在 build.gradle 中添加任务:
task getDeps(type: Copy) {
from sourceSets.main.runtimeClasspath
into 'runtime/'
}
运行 gradle getDeps
,会将依赖的jar下载到 runtime 目录。
示例 2
在 build.gradle 中添加任务:
task copyDeps(type: Copy) {
from (configurations.compile + configurations.testCompile) {
include "*.jar"
include "*.so", "*.dll"
}
into rootProject.rootDir.getAbsolutePath() + "/lib"
}
运行 gradle copyDeps
,会将依赖的jar下载到 lib 目录。
参考
- How to download dependencies in gradle
- How to download maven dependencies into project local directory and set eclipse classpath?
将 maven 项目转换为 gradle 项目
进入项目根目录,也就是有 pom.xml 文件的目录,执行:
$ gradle init
会提示要不要转换成 gradle 项目,根据提示操作即可。
操作完成后,会生成 build.gradle、settings.gradle 文件。
清理 Gradle 生成的 build、out 目录
build、out 目录会存放缓存、class文件等。使用 Intellij IDEA 编写代码时,有时会遇到缓存、class文件内容和实际代码不一样的情况,会发生一些很诡异的事情,比如运行结果不符合预期、运行报错等。
这个不一样可能是:
- 代码中挪动类的位置了,缓存中没挪。
- 代码中某个资源文件更新了,但是缓存中没更新。
- 等等
解决办法是删除这些目录。手动删,或者写个 gradle task 删除:
task cleanBuildDir(type: Delete) {
delete "${projectDir}/build"
delete "${projectDir}/out"
}
如果一个项目中有多个子项目,那么会有很多 build、out 目录, 可以这样写 task :
allprojects {
task cleanBuildDir(type: Delete) {
delete "${projectDir}/build"
delete "${projectDir}/out"
}
}
执行顶层项目的 cleanBuildDir 任务即可。
使用 Gradle 将源码打包为 jar
在 build.gradle 中应用 java
插件后,默认会有一个 jar 任务用于打包。在 Gradle 项目根目录执行下面命令即可:
$ gradle jar
但是,该任务默认不会将依赖打包进去(也就是不会打包成 fat jar)。如果要将依赖打包进去,可以修改 jar 任务的配置:
jar {
manifest {
attributes 'Main-Class': 'com.example.App' // 启动类全路径,需要根据项目自定义,或者不配置
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
或者自定义一个 fatJar 任务:
task fatJar(type: Jar) {
manifest {
attributes 'Main-Class': 'com.example.App'
}
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
with jar
}
测试示例1
Java 版本:1.8 Gradle 版本: 5.2
项目结构:
.
├── build.gradle
├── settings.gradle
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ ├── App.java
│ │ └── Utils.java
│ └── resources
└── test
├── java
└── resources
settings.gradle :
rootProject.name = 'test-gradle-jar-01'
build.gradle :
plugins {
id 'java'
}
group 'com.example'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
}
Utils.java :
package com.example;
public class Utils {
public static int add(int a, int b) {
return a + b;
}
}
App.java :
package com.example;
public class App {
public static void main(String[] args) {
System.out.println("1 + 1 = " + Utils.add(1, 1));
}
}
在项目根目录执行 gradle jar
命令,然后可以在 build/libs
目录中找到打包好的 jar :
$ ls build/libs
test-gradle-jar-01-1.0-SNAPSHOT.jar
# 查看 jar 中文件
$ jar tf build/libs/test-gradle-jar-01-1.0-SNAPSHOT.jar
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/App.class
com/example/Utils.class
# 执行
$ java -jar build/libs/test-gradle-jar-01-1.0-SNAPSHOT.jar
build/libs/test-gradle-jar-01-1.0-SNAPSHOT.jar中没有主清单属性
# 上面的执行报错了,原因是 MANIFEST.MF 没指定主类,
$ unzip -q -c build/libs/test-gradle-jar-01-1.0-SNAPSHOT.jar META-INF/MANIFEST.MF
Manifest-Version: 1.0
# 执行
$ java -cp build/libs/test-gradle-jar-01-1.0-SNAPSHOT.jar com.example.App
1 + 1 = 2
测试示例2
项目结构:
.
├── build.gradle
├── settings.gradle
└── src
└── main
├── java
│ └── com
│ └── example
│ └── App.java
└── resources
└── test.txt
settings.gradle :
rootProject.name = 'test-gradle-jar-02'
build.gradle :
plugins {
id 'java'
}
group 'com.example'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
maven { url 'http://mirrors.cloud.tencent.com/nexus/repository/maven-public/' }
mavenLocal()
mavenCentral()
}
dependencies {
compile group: 'com.google.guava', name: 'guava', version: '28.2-jre'
}
test.txt :
Hello World
App.java :
package com.example;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import java.io.IOException;
import java.net.URL;
public class App {
public static void main(String[] args) throws IOException {
URL url = Resources.getResource("test.txt");
String content = Resources.toString(url, Charsets.UTF_8);
System.out.println(content);
}
}
使用 gradle jar 打包后,执行 App 类会失败。
$ java -cp build/libs/test-gradle-jar-02-1.0-SNAPSHOT.jar com.example.App
Exception in thread "main" java.lang.NoClassDefFoundError: com/google/common/io/Resources
at com.example.App.main(App.java:12)
Caused by: java.lang.ClassNotFoundException: com.google.common.io.Resources
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:338)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 1 more
没找到 guava 中的 Resources 类,所以报错了。
解决方法1:在 classpath 中指定依赖路径
在 build.gradle 中增加下面的任务:
task getDeps(type: Copy) {
from sourceSets.main.runtimeClasspath
into 'runtime/'
}
执行 gradle getDeps
下载依赖到 runtime
目录。
运行 App 类:
# 方式1
$ java -cp build/libs/test-gradle-jar-02-1.0-SNAPSHOT.jar:runtime/guava-28.2-jre.jar com.example.App
Hello World
# 方式2
$ java -cp build/libs/test-gradle-jar-02-1.0-SNAPSHOT.jar:"runtime/*" com.example.App
Hello World
解决方法2:构造 fat jar
将 build.gradle 修改如下:
plugins {
id 'java'
}
group 'com.example'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
maven { url 'http://mirrors.cloud.tencent.com/nexus/repository/maven-public/' }
mavenLocal()
mavenCentral()
}
task getDeps(type: Copy) {
from sourceSets.main.runtimeClasspath
into 'runtime/'
}
// 构建 fat jar
task fatJar(type: Jar) {
manifest {
attributes 'Main-Class': 'com.example.App'
}
baseName = project.name + '-fatJar'
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
with jar
}
dependencies {
compile group: 'com.google.guava', name: 'guava', version: '28.2-jre'
}
执行 gradle fatJar
,在 build/libs 目录会生成test-gradle-jar-02-fatJar-1.0-SNAPSHOT.jar
。
执行 App 类:
$ java -cp build/libs/test-gradle-jar-02-fatJar-1.0-SNAPSHOT.jar com.example.App
Hello World
因为在清单文件中指定了主类,也可以用下面的方式执行:
$ java -jar build/libs/test-gradle-jar-02-fatJar-1.0-SNAPSHOT.jar
Hello World
fatJar 长什么样子 ?
$ jar tf build/libs/test-gradle-jar-02-fatJar-1.0-SNAPSHOT.jar
META-INF/MANIFEST.MF
com/google/common/util/concurrent/Striped.class
... 省略部分内容
com/google/j2objc/annotations/Weak.class
com/example/
com/example/App.class
test.txt
看一下 META-INF/MANIFEST.MF 的内容:
$ unzip -q -c build/libs/test-gradle-jar-02-fatJar-1.0-SNAPSHOT.jar META-INF/MANIFEST.MF
Manifest-Version: 1.0
Main-Class: com.example.App
测试示例3
这个一个多模块的项目。
项目结构:
.
├── build.gradle
├── settings.gradle
├── src
│ └── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ └── App.java
│ └── resources
│ └── test.txt
└── sub-project
└── src
└── main
├── java
│ └── com
│ └── example
│ └── sub
│ └── Utils.java
└── resources
├── test.txt
└── test2.txt
settings.gradle :
rootProject.name = 'test-gradle-jar-03'
include 'sub-project'
build.gradle :
plugins {
id 'java'
}
group 'com.example'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
allprojects {
apply plugin: 'java'
repositories {
maven { url 'http://mirrors.cloud.tencent.com/nexus/repository/maven-public/' }
mavenLocal()
mavenCentral()
}
}
jar {
manifest {
attributes 'Main-Class': 'com.example.App'
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
project(":sub-project") {
dependencies {
compile group: 'com.google.guava', name: 'guava', version: '28.2-jre'
}
}
dependencies {
compile project(":sub-project")
}
sub-project/src
目录下的 test.txt :
子项目: 你好
sub-project/src
目录下的 test2.txt :
子项目: 你好2
sub-project/src
目录下的 Utils.java :
package com.example.sub;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import java.io.IOException;
import java.net.URL;
public class Utils {
public static int add(int a, int b) {
return a + b;
}
public static String readResourceFileContent(String path) {
try {
URL url = Resources.getResource(path);
return Resources.toString(url, Charsets.UTF_8);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
src
目录下的 test.txt :
Hello World
src
目录下的 App.java :
package com.example;
import com.example.sub.Utils;
public class App {
public static void main(String[] args) {
System.out.println("test.txt 内容: " + Utils.readResourceFileContent("test.txt"));
System.out.println("test2.txt 内容: " + Utils.readResourceFileContent("test2.txt"));
System.out.println("1+1 = " + Utils.add(1, 1));
}
}
执行 gradle jar
打包。
执行 jar:
$ java -jar build/libs/test-gradle-jar-03-1.0-SNAPSHOT.jar
test.txt 内容: 子项目: 你好
test2.txt 内容: 子项目: 你好2
1+1 = 2
题外话:jar 中出现同路径、同名文件怎么办
上面的代码中,有两个 test.txt 文件。
在执行jar时,发现 test.txt 的内容是 sub-project 下的文件内容:
$ java -jar build/libs/test-gradle-jar-03-1.0-SNAPSHOT.jar
test.txt 内容: 子项目: 你好
test2.txt 内容: 子项目: 你好2
1+1 = 2
这并不意味着只有 sub-project 下的 test.txt 被放到了 jar 中,实际上两个 test.txt 都被打包进去了。
$ jar tf build/libs/test-gradle-jar-03-1.0-SNAPSHOT.jar | grep "test"
test.txt
test.txt
test2.txt
为什么 ? jar 其实使用 zip 打包,而zip内部支持同路径、同名文件出现多次。不过我们的操作系统只允许一次,所以讲jar解压后,只会保留一个 test.txt 。java 在运行时也只会保留一个。
guava 的 Resources.getResource 底层用的 ClassLoader 下的 getResource 方法,该方法只会返回一个 URL。而 ClassLoader 下的 getResources 可以返回多个 URL 。我们用 getResources 测试下。
增加 App2.java 类:
package com.example;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
public class App2 {
public static void main(String[] args) throws IOException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 符合要求的文件可能不止一个
Enumeration<URL> urlList = classLoader.getResources("test.txt");
while (urlList.hasMoreElements()) {
URL url = urlList.nextElement();
System.out.printf("%s 内容: %s\n", url.getPath(), Resources.toString(url, Charsets.UTF_8));
}
}
}
使用 gradle jar
打包后执行 App2 :
$ java -cp build/libs/test-gradle-jar-03-1.0-SNAPSHOT.jar com.example.App2
file:/path/to/build/libs/test-gradle-jar-03-1.0-SNAPSHOT.jar!/test.txt 内容: 子项目: 你好
但是,在 Intellij IDEA 中执行 App2.java ,会输出两个:
/path/to/out/production/resources/test.txt 内容: Hello World
/path/to/sub-project/out/production/resources/test.txt 内容: 子项目: 你好
这是IDE的运行机制不同导致的,IDEA 在执行 App2.java 的 java -classpath
参数中, 有下面一段内容:
/path/to/out/production/classes:/path/to/out/production/resources:/path/to/sub-project/out/production/classes:/path/to/sub-project/out/production/resources:
这意味着同一个jar、同一个目录下只能有一个同名文件生效。
我们再测试下。
在 build/libs
目录中创建 test.txt ,内容是:
窗前明月光
再次执行 jar 中的 App2,但 classpath 中增加该目录:
$ java -cp build/libs/test-gradle-jar-03-1.0-SNAPSHOT.jar:build/libs/ com.example.App2
file:/path/to/build/libs/test-gradle-jar-03-1.0-SNAPSHOT.jar!/test.txt 内容: 子项目: 你好
/path/to/build/libs/test.txt 内容: 窗前明月光
如果再把 test.txt 打包到test.jar
中,再执行 App2 :
$ java -cp build/libs/test-gradle-jar-03-1.0-SNAPSHOT.jar:build/libs/:build/libs/test.jar com.example.App2
file:/path/to/build/libs/test-gradle-jar-03-1.0-SNAPSHOT.jar!/test.txt 内容: 子项目: 你好
/path/to/build/libs/test.txt 内容: 窗前明月光
file:/path/to/build/libs/test.jar!/test.txt 内容: 窗前明月光