Gradle 历险记(四):正确的打包jar的方式

gradle 自身不提供打包 jar 的功能,后人各种自定义、乱写、以讹传讹,导致经常打包出来的 jar 都不能用,本文介绍三种方式,一种存在风险的方式,两种正确的方式。

一、存在风险的打包方式——修改 task jar来生成fatjar

这是网上很常见的打包方式,一搜一大片,大概长这样:

1
2
3
4
5
6
7
8
9
10
11
jar {
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) }
}
exclude('LICENSE.txt', 'NOTICE.txt', 'rootdoc.txt')
exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA'
exclude 'META-INF/NOTICE', 'META-INF/NOTICE.txt'
exclude 'META-INF/LICENSE', 'META-INF/LICENSE.txt'
exclude 'META-INF/DEPENDENCIES'
}

原理就是执行jar这个task时,将所有的依赖全部解压出来,跳过特定文件名的文件。

使用方法:gradle build ,在build/libs/ 里生成文件,正常情况下不会出问题,编译出来的 jar 里包含了无数个class和资源文件,但是!!!!! 可能会在特定情况下出现问题!可能会在特定情况下出现问题!可能会在特定情况下出现问题! 因为这个逻辑太简单了,是强行解压所有的文件,在依赖非常多的情况下,可能资源文件会相互覆盖(实测的时候,会出现大量的同名文件)。

这种情况我只触发过一次,在某个项目里,使用gradle2.14稳定复现,gradle4.1就没问题,因为依赖之间打起来了,导致 log 无法打出来。 所以如果使用这种方法的话,一定要心里有 B 数,尽量用新版的gradle。 当然这里也有其他的写法,有种使用classpath和原版jar的应该是没啥问题的。

二、使用shadow plugin 生成 fatjar

插件地址: https://github.com/johnrengelman/shadow 这是个大佬写的轮子,应该是maven时代 maven-shade-plugin 的 gradle 版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apply plugin: 'com.github.johnrengelman.shadow'
sourceCompatibility = 1.8

buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "com.github.jengelman.gradle.plugins:shadow:1.2.3"
}
}

shadowJar {
baseName = 'hello'
classifier = null
version = null
zip64 true

manifest {
attributes 'Main-Class': 'com.example.Main'
}
}

使用方法 gradle shadowJar ,在build/libs 里生成文件。目前运行稳定,没遇到什么问题。 原理没有仔细看,结果就是个 fatjar,应该是经过一点处理的,否则也会像第一个方案一样,因为冲突导致依赖挂掉。

三、使用springboot plugin 生成带有spring启动器的jar(强烈推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
buildscript {
ext {
springBootVersion = '1.5.11.RELEASE'
}
repositories {
mavenLocal()
mavenCentral()
jcenter()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion")
}
}
apply plugin: 'org.springframework.boot'
springBoot {
mainClass = "com.example.Main"
}

虽然说SpringBoot常常拿来写 web,但它还是一个很好用的打包工具,不会引入过多的乱七八糟的东西。 使用方式是gradle build ,打出来的是有特定结构的 jar,四部分,本项目的class文件、依赖项目的 jar 包、Manifest、Spring 启动器。 这个很稳,而且配置也超简单,强烈推荐!

四、jar 包运行大致流程

(这部分是根据表现推断的,没有阅读过相关代码)

  1. 访问 jar 包的 manifest 文件,寻找其中的 'Main-Class',再寻找public static void main(String args[])
  2. 访问 jar 包的 manifest 文件,获取其中的 'Class-Path',作为可能存放代码的地方
  3. 执行代码,遇到没见过的class就去放 class 的地方找,再到classpath里找,找不到的话就报错