Java 打包 FatJar ⽅法⼩结
阿⾥云云栖社区
. . . 字数 , 阅读 ,
在函数计算(Aliyun FC)中发布⼀个 Java 函数,往往需要将函数打包成⼀个 all-in-one 的 zip 包或 者 jar 包。Java 中这种打包 all-in-one 的技术常称之为 Fatjar 技术。本⽂⼩结⼀下 Java ⾥打包 FatJar 的若⼲种⽅法。
什么是 FatJar
FatJar ⼜称作 uber-Jar,是包含所有依赖的 Jar 包。Jar 包中嵌⼊了除 java 虚拟机以外的所有依 赖。我们知道 Java 的依赖分为两种, 零散的 .class ⽂件和把多个 .class ⽂件以 zip 格式打包⽽
成 jar ⽂件。FatJar 是⼀个 all-in-one Jar 包。FatJar 技术可以让那些⽤于最终发布的 Jar 便于部 署和运⾏。
三种打包⽅法
我们知道 .java 源码⽂件会被编译器编译成字节码.class ⽂件。Java 虚拟机执⾏的是 .class ⽂ 件。⼀个 java 程序可以有很多个 .class⽂件。这些 .class ⽂件可以由 java 虚拟机的类装载器运⾏
期装载到内存⾥。java 虚拟机可以从某个⽬录装载所有的 .class ⽂件,但是这些零散的.class ⽂ 件并不便于分发。所有 java ⽀持把零散的.class ⽂件打包成 zip 格式的 .jar ⽂件,并且虚拟机的 类装载器⽀持直接装载 .jar ⽂件。
⼀个正常的 java 程序会有若⼲个.class ⽂件和所依赖的第三⽅库的 jar ⽂件组成。
. ⾮遮蔽⽅法(Unshaded)
⾮遮蔽是相对于遮蔽⽽说的,可以理解为⼀种朴素的办法。解压所有 jar ⽂件,再重新打包成⼀
个新的单独的 jar ⽂件。
借助 Maven Assembly Plugin 都可以轻松实现⾮遮蔽⽅法的打包。
Maven Assembly Plugin
Maven Assembly Plugin 是⼀个打包聚合插件,其主要功能是把项⽬的编译输出协同依赖,模 块,⽂档和其他⽂件打包成⼀个独⽴的发布包。使⽤描述符(descriptor)来配置需要打包的物 料组合。并预定义了常⽤的描述符,可供直接使⽤。
预定义描述符如下
• bin 只打包编译结果,并包含 README, LICENSE 和 NOTICE ⽂件,输出⽂件格式为 tar.gz, tar.bz 和 zip。
• jar-with-dependencies 打包编译结果,并带上所有的依赖,如果依赖的是 jar 包,jar 包会被 解压开,平铺到最终的 uber-jar ⾥去。输出格式为 jar。
• src 打包源码⽂件。输出格式为 tar.gz, tar.bz 和 zip。
• project 打包整个项⽬,除了部署输出⽬录 target 以外的所有⽂件和⽬录都会被打包。输出格式 为 tar.gz, tar.bz 和 zip。
除了预定义的描述符,⽤户也可以指定描述符,以满⾜不同的打包需求。
打包成 uber-jar,需要使⽤预定义的 jar-with-dependencies 描述符:
在 pom.xml 中加⼊如下配置
关注
Gradle Java plugin
gradle 下打包⼀个⾮遮蔽的 jar 包,有不少插件可以⽤,但是由于 gradle ⾃身的灵活性,可以直 接⽤ groove 的 dsl 实现。
⾮遮蔽⽅法会把所有的 jar 包⾥的⽂件都解压到⼀个⽬录⾥,然后在打包同⼀个 fatjar 中。
对于复杂应⽤很可能会碰到同名类相互覆盖问题。
. 遮蔽⽅法(Shaded)
遮蔽⽅法会把依赖包⾥的类路径进⾏修改到某个⼦路径下,这样可以⼀定程度上避免同名类相互 覆盖的问题。最终发布的 jar 也不会带⼊传递依赖冲突问题给下游。
Maven Shade Plugin 在 pom.xml 中加⼊如下配置
Gradle Shadow plugin
Gradle shadow plugin 使⽤⾮常简单,简单声明插件后就可以⽣效。
遮蔽⽅法依赖修改 class 的字节码,更新依赖⽂件的包路径达到规避同名同包类冲突的问 题,但是改名也会带来其他问题,⽐如代码中使⽤ Class.forName 或
ClassLoader.loadClass 装载的类,Shade Plugin 是感知不到的。同名⽂件覆盖问题也没法 杜绝,⽐如META-INF/services/javax.script.ScriptEngineFactory不属于类⽂件,但是被覆 盖后会出现问题。
. 嵌套⽅法(Jar of Jars)
还是⼀种办法就是在 jar 包⾥嵌套其他 jar,这个⽅法可以彻底避免解压同名覆盖的问题,但是这 个⽅法不被 JVM 原⽣⽀持,因为 JDK 提供的 ClassLoader 仅⽀持装载嵌套 jar 包的 class ⽂件。
所以这种⽅法需要⾃定义 ClassLoader 以⽀持嵌套 jar。
Onejar Maven Plugin
One-JAR 就是⼀个基于上⾯嵌套 jar 实现的⼯具。onejar-maven-plugin 是社区基于 onejar 实现 的 maven 插件。
Spring boot plugin
One-JAR 有点年久失修,好久没有维护了,Spring Boot 提供的 Maven Plugin 也可以打包 Fatjar,⽀持⾮遮蔽和嵌套的混合模式,并且⽀持 maven 和 gradle 。
requiresUnpack 参数可以定制那些 jar 不希望被解压,采⽤嵌套的⽅式打包到 Fatjar 内部。
其打包后的内部结构为
应⽤的类⽂件被放置到 BOOT-INF/classes ⽬录,依赖包被放置到 BOOT-INF/lib ⽬录。
查看 META-INF/MANIFEST.MF ⽂件,其内容为
启动类是固定的 org.springframework.boot.loader.JarLauncher,应⽤程序的⼊⼝类需要配置成 Start-Class。这样做的⽬的主要是为了⽀持嵌套 jar 包的类装载,替换掉默认的 ClassLoader。
但是函数计算 Java Runtime 需要的 jar 包是⼀种打包结构,在服务端运⾏时会解压 开,./lib ⽬录加到 classpath 中,单不会调⽤ Main-Class。所以⾃定义 ClassLoader 是不
⽣效的,所以不要使⽤嵌套 jar 结构,除⾮在⼊⼝函数指定重新定义 ClassLoader 或者 classpath 以⽀持 BOOT-INF/classes 和 BOOT-INF/lib 这样的定制化的类路径。
⼩结
单从 Fatjar 的⻆度看, Spring boot maven/gradle 做得最精致。但是 jar 包内部的⾃定义路径解 压开以后和函数计算是不兼容的。所以如果⽤于函数计算打包,建议使⽤ Unshaded 或者 Shared 的打包⽅式,但是需要⾃⼰注意⽂件覆盖问题。
本⽂作者:倚贤 阅读原⽂
本⽂为云栖社区原创内容,未经允许不得转载。