在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功能
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。