共计 2413 个字符,预计需要花费 7 分钟才能阅读完成。
在 Java 里,如果要运行一个 Java 程序,那么须要一个蕴含 main 办法 Class 的 Jar 包或者 Class 文件,而后执行命令
# 运行一个 class 文件
java $class
# 运行一个 Java 包(Manifest 中指定了 Main-Class)java -jar $jarfile
# 运行一个 Class 的 main 办法,并且将多个 jar 包增加到运行时的 classpath
java -cp(-classpath) $path(目录 /jar 文件 /zip 文件) #zip 文件须要合乎 jar 格局的标准
这种形式会有一些弊病,因为大多数的 Java 程序里,会蕴含很多依赖,如果要启动这个程序就必须要通过 -classpath
来指定这些依赖包文件,而这些 classpath 的指定还必须得在服务器下来指定,不是很不便
尤其是在 DevOps/ 微服务的流行下,这种指定 classpath 的形式显得太不灵便了,能不能做到间接构建一个聚合的 jar 包,执行公布或者运行呢?
Executable Jar
Executable Jar(可执行的 jar 包),个别是指将所有依赖的 jar 包都放在一个 Jar 包内,这个 Jar 包蕴含了所有运行时须要依赖的 Jar 包代码,不过并没有规定这个“放”的形式,能够是将依赖的 jar 包间接放在入口 jar 包内,就像这样
├─executable jar
│ ├─META-INF
│ │ ├─MANIFEST.MF
│ ├─com...
│ ├─lib
│ │ ├─io.netty....jar
│ │ ├─com.google....jar
│ │ ├─com.github....jar
│ │ ├─org.apache.....jar
也能够是将依赖的 jar 包内的文件给拷贝到入口的 Jar 包内,就像这样:
├─executable jar
│ ├─META-INF
│ │ ├─MANIFEST.MF
│ ├─com...
│ ├─io.netty.... classes
│ ├─com.google.. classes
│ ├─com.github.. classes
│ ├─org.apache.. classes
Spring Boot 算是把 Executable Jar 传到了千家万户,Spring Boot 中提供了一个 Maven 插件spring-boot-maven-plugin
,这个插件能够将你所有的 Maven 依赖 Jar 包在构建时打包到一个 jar 文件内,并且通过 Spring Boot 的 ClassLoader 和启动类,能够加载到这些 Executable Jar 包中的 Jar 包,就是下面介绍的第一种形式:将依赖的 jar 包间接放在入口 jar 包内
Uber Jar
第一次看到这个词的时候,一头雾水,不晓得这个单词是个什么鬼意思,Uber 打车?
在查阅材料后才晓得,Uber Jar 的原单词是Über Jar
,是德语单词,能够解释为 ”Over”,完结的意思,不过在理论的上下文中,翻译为“所有”可能更适合。
这个术语最后是由开发人员发明的,他们认为将所有依赖项和本人的代码放入一个 jar 文件中能够解决很多抵触问题 。然而大多数输入法上德语Ü
很难打进去,所以就成了 ”Uber “。
Fat jar
Fat 翻译为肥的、胖的、大的,Fat jar 就很好解释了:一个“瘦削的”jar,和 Uber jar 表白的含意,指的是蕴含所有依赖包的 Jar 包
Shade Jar/Shadow Jar
shade
机翻为暗影、遮蔽,shade jar 是指将 jar 包及其依赖包打包到一个 jar 文件内,同时提供 shade“遮蔽 / 重命名”某些依赖包的性能
比方一个 Maven 工程,依赖了很多三方包,但理论打包时你想重命名局部包,这个重命名的过程在这里能够叫 shade
这里具体解释下“shade”,shade 在这个过程里的目标时重命名某些包,那么为什么要重命名呢?
比方咱们基于 JAVA Instrumentation API 去开发一个 Agent,咱们在开发这个 Agent 时也须要依赖一些三方包,比方 Netty,那么在理论运行时须要通过 -javaagent 或者动静 attach agent jar 的模式去加载咱们这个 Agent jar 包;
这里加载的 Agent 只能是一个独立的 jar 包,所以首先要将咱们的 agent 和依赖的包都打到一个 jar 包内,构建一个 ”Uber jar”;而后须要思考 Class 包抵触的问题,因为 Agent 内的依赖包的 Class,和指标 JVM 过程中的 Class 很可能是抵触的,比方 Agent 中依赖了 netty 4.1.58.Final,而指标 JVM 过程中依赖了 netty 4.0.14.Final,而咱们的 Agent 中应用了 4.0.14 中不存在的 API(比方某个类的办法,是新版本新增的);此时程序就会出错了,NoSuchMethodError,因为指标过程曾经加载了那个 Class,Agent 包中的同名 Class 不会被反复加载
这里解决这种抵触问题的话,有一个简略的计划,在构建 ”Uber jar” 时,将依赖的包的包名批改,重定位(relocaiton),比方将 io.netty
批改为com.github.kongwu.io.netty
,同时将 Java 代码中所有的援用都批改为重定位之后的包名。这样,通过批改包名,就完满避开了依赖包 Class 抵触的问题
而下面这个“重定位”的行为,就叫 Shade,或者叫 Shadow
Maven 中的 Shade 插件为 https://maven.apache.org/plugins/maven-shade-plugin/,能够将程序打包成一个独立的 Jar 包,蕴含依赖包
另一款相似的 Maven 插件为 https://maven.apache.org/plugins/maven-assembly-plugin/usage.html,也能够实现同样的成果
Gradle 中也有相似的插件 https://imperceptiblethoughts.com/shadow/,性能也很弱小,也反对 shade 性能