什么是 FatJar

FatJar 又叫 uber-jar。uber 不是打车的 uber,而是德语里面的 uber,意思是英语里面的 over-勉强可以翻译为超越。

FatJar 是一个 all-in-one 的 jar,它可以让部署和运行更加便利,它让最终部署和运行的环境不依赖于任何 maven 或者 lib 的 classpath。

FarJar 的三种具体类型

非遮蔽的(Unshaded)

依赖于maven-assembly-plugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>CHOOSE LATEST VERSION HERE</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>assemble-all</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>

descriptorRef 有:

  • bin 只打包编译结果,并包含 README, LICENSE 和 NOTICE 文件,输出文件格式为 tar.gz, tar.bz2 和 zip。
  • jar-with-dependencies 打包编译结果,并带上所有的依赖,如果依赖的是 jar 包,jar 包会被解压开,平铺到最终的 uber-jar 里去。输出格式为 jar。
  • src 打包源码文件。输出格式为 tar.gz, tar.bz2 和 zip。
  • project 打包整个项目,除了部署输出目录 target 以外的所有文件和目录都会被打包。输出格式为 tar.gz, tar.bz2 和 zip。

所有的 jar 都被 unpack,然后 repack。

和 java 的缺省类加载器一起工作。

遮蔽的(Shaded)

依赖于maven-shade-plugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<relocations>
<relocation>
<pattern>org.apache.commons</pattern>
<shadedPattern>com.acme.shaded.apachecommons</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>

所有的 jar 都被 unpack,然后 repack,而且被刻意 rename(所以叫 shade),以避免 dependency version clashes。这种 rename 会产生字节码级的变动,使得类的 package 变化。

和 java 的缺省类加载器一起工作。

shaded jar 依然有可能导致版本冲突,所以需要依赖 class-relocation 解决类重定位的问题,依赖 Resource Transformers 解决资源重定位的问题。

shaded jar 是为了解决这样的问题:

A 依赖 b/c,b/c依赖于 d 的两个版本。如果不产生 shaded,则 b 和 c 必有一段分支路径运行时失败。如果让 b 使用 shaded 过的 d,产生一个 shaded-b,则这个 shaded-b 本身包含引用了改名的 d,c 依赖于正常的 d,两段执行路径都可要正常运行完。

JAR of JARs

只是把 jar 打包在一起,jar 里有 jar。

我们常见的 maven package 无插件操作打出来的 jar 就是这种 jar。

默认的 fatjar 里不一定包含所有的依赖,所以需要使用插件:

1
2
3
4
5
6
7
8
9
10
11
12
<plugin>
<groupId>com.jolira</groupId>
<artifactId>onejar-maven-plugin</artifactId>
<version>1.4.4</version>
<executions>
<execution>
<goals>
<goal>one-jar</goal>
</goals>
</execution>
</executions>
</plugin>

其他 jar 的分类

  • Skinny – Contains ONLY the bits you literally type into your code editor, and NOTHING else.
  • Thin – Contains all of the above PLUS the app’s direct dependencies of your app (db drivers, utility libraries, etc).
  • Hollow – The inverse of Thin – Contains only the bits needed to run your app but does NOT contain the app itself. Basically a pre-packaged “app server” to which you can later deploy your app, in the same style as traditional Java EE app servers, but with important differences.
  • Fat/Uber – Contains the bit you literally write yourself PLUS the direct dependencies of your app PLUS the bits needed to run your app “on its own”.

Spring Boot 与 FatJar

实际上 Java 的原生类加载器处理普通的 Jar 里面的嵌套 class 是友好的,但处理嵌套的 jar 是不友好的。

Spring Boot 的 jar 就是 fatjar,这种 fatjar 携带所有依赖,而且有专有的类加载器来处理嵌套 jar 的依赖问题。这种 fatjar 是最简单的,运行起来最友好的。

分析依赖本来要逐层解压这种 jar-of-jars,但很多解析工具只解析一层的话,反而会被其他问题触发。例如,有时候为了解决依赖版本 冲突而指定 jar 的版本,而直接在一个多模块的 parent pom 里面短路地指定了一个依赖版本,反而会触发解析工具的检测规则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.3.1.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>

<!-- 使用本插件打包非 Spring-Boot 专有程序 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layout>ZIP</layout>
<requiresUnpack>
<dependency>
<groupId>org.jruby</groupId>
<artifactId>jruby-complete</artifactId>
</dependency>
</requiresUnpack>
</configuration>
</plugin>

这样会打印出两个 jar,一个 jar 是普通 jar,另一个 jar 是 jar.original。第二个 jar 是原始 jar,而第一个 jar 则是大而全的真正的 fat-jar。

参考文献:

  1. 《Java 打包 FatJar 方法小结》
  2. 《What is an uber jar?》
  3. 《可执行的uberJar (fatJar)》