在这篇文章中,我们描述了三种创建独立的可执行JAR的方法。
当您的应用程序超出了十几行代码时,您可能应该将代码分成多个类。在Java中,经典打包格式是Java ARchive,也称为JAR。但是实际应用程序可能依赖于其他JAR包。
这篇文章旨在描述创建独立的可执行JAR(也称为uber-JAR或胖JAR)的方法。
什么是可执行JAR?
JAR只是类文件的集合。为了可执行,其META-INF/MANIFEST.MF文件应指向实现该main()方法的类。您可以使用Main-Class属性来执行此操作。这是一个例子:
Main-Class: path.to.MainClass
MainClass有一个static main(String… args)方法 |
处理类路径
大多数应用程序依赖现有代码。Java提供了类路径classpath的概念。类路径是运行时将查找以查找依赖代码的路径元素的列表。当运行Java类,定义通过类路径中-cp的命令行选项:
java -cp lib/one.jar;lib/two.jar;/var/lib/three.jar path.to.MainClass |
Java运行时通过聚合来自所有引用的JAR的所有类并添加主类来创建类路径。
分发依赖于其他JAR的JAR时会出现新的问题:
- 您需要在相同版本中定义相同的库
- 更重要的是,该-cp参数不适用于JARs。要引用其他JAR,需要在JAR清单中通过Class-Path属性设置类路径:Class-Path: lib/one.jar;lib/two.jar;/var/lib/three.jar
- 因此,您需要根据清单将JAR放在目标文件系统上的相对或绝对*相同的位置。这意味着要打开JAR并先阅读清单。
Apache Assembly插件
Maven的Assembly Plugin使开发人员能够将项目输出组合到一个可分发的存档中,该存档还包含依赖项,模块,站点文档和其他文件。
Assembly插件依赖于特定的assembly.xml配置文件。它允许您选择要包含在工件中的文件。请注意,最终的工件不必是JAR:配置文件可让您在可用格式(例如zip,war等)之间进行选择。
该插件通过提供预定义的程序集来管理常见的用例。自包含的JAR的分布在其中。配置如下pom.xml所示:
<plugin> |
上述步骤:
- 参考预定义的自包含JAR配置
- 设置要执行的主类
- 执行single目标
- 将目标绑定到package阶段,即在构建原始JAR之后
运行mvn package产生两个工件:
- <name>-<version>.jar
- <name>-<version>-with-dependencies.jar
第一个JAR的内容与没有该插件时创建的内容相同。第二个是独立的JAR。您可以像这样执行它:
java -jar target/executable-jar-0.0.1-SNAPSHOT.jar
根据项目的不同,它可能会成功执行... 例如,它在示例Spring Boot项目中失败,并显示以下消息:
%d [%thread] %-5level %logger - %msg%n java.lang.IllegalArgumentException: |
原因是在同一路径下不同的JAR提供不同的资源文件,例如 META-INF/spring.factories。该插件遵循最后写入胜出策略覆盖相同资源文件。顺序是基于JAR的名称。
使用Assembly,您可以排除资源,但不能合并它们。当您需要合并资源时,您可能需要使用Apache Shade插件。
Apache Shade插件
Assembly插件是通用的;Shade插件仅专注于创建独立的JAR的任务。
该插件提供了将工件打包(包括其依赖项)并遮蔽(即重命名)某些依赖项的包的功能。
该插件基于转换器的概念:每个转换器负责处理一种类型的资源。转换器可以按原样复制资源,添加静态内容,将其与其他资源合并等。
虽然您可以开发一个转换器,但是该插件提供了一组现成的转换器:
ApacheLicenseResourceTransformer:防止许可证重复 |
上面程序集的Shade插件配置如下:
<plugin> |
上述配置说明:
- 默认情况下,shade目标已绑定到package阶段
- 该转换器专用于生成清单文件
- 设置Main-Class条目
- 将最终的JAR配置为多发行版JAR。当任何初始JAR是多发行版JAR时,这都是必需的
运行mvn package产生两个工件:
- <name>-<version>.jar:自包含的可执行文件JAR
- original-<name>-<version>.jar:没有嵌入式依赖项的“普通”
对于示例项目,最终的可执行文件仍然无法按预期工作。确实,在构建期间有很多关于重复资源的警告。其中两个阻止示例项目正常工作。为了正确地合并它们,我们需要看一下它们的格式:
- META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat:此Log4J2文件包含预编译的Log4J2插件数据。它以二进制格式编码,任何开箱即用的转换器都无法合并此类文件。然而,随便搜索发现有人已经遇到了这个问题,并发布了一个转换器来处理合并。
- META-INF/spring.factories:这些特定于Spring的文件具有单键/多值格式。虽然它们是基于文本的,但没有开箱即用的转换器可以正确合并它们。但是,Spring开发人员在其插件中提供了此功能(以及更多)。
要配置这些转换器,我们需要将以上库作为依赖项添加到Shade插件中:
<plugin> |
上述配置说明:
- 合并Log4J2.dat文件
- 合并/META-INF/spring.factories档案
- 添加所需的变压器代码
此配置有效!仍然有剩余警告:
- Manifests
- Licenses, notices and similar files
- Spring Boot specific files i.e. spring.handlers, spring.schemas and spring.tooling
- Spring Boot-Kotlin specific files e.g. spring-boot.kotlin_module, spring-context.kotlin_module, etc.
- Service loader configuration files
- JSON files
您可以添加和配置其他变压器来修复其余的警告。总而言之,整个过程需要对每种资源以及如何使用它们有深刻的理解。
Spring Boot插件
Spring Boot插件采用了完全不同的方法。它不会单独合并来自JAR的资源。它增加了相关的JAR ,因为它们是 uber JAR。为了加载类和资源,它提供了一种特定的类加载机制。显然,它专用于Spring Boot项目。
配置Spring Boot插件很简单:
<plugin> |
让我们检查一下最终JAR的结构:
/ |
目录说明:
- classes:项目编译类
- lib:JAR依赖
- loader:Spring Boot类加载类
这是我们的示例项目清单文件配置:
Main-Class: org.springframework.boot.loader.JarLauncher |
如您所见,主类是特定于Spring Boot的类,而“真实”主类在另一个条目下被引用。
有关JAR结构的更多信息,请参阅参考文档。
结论
在这篇文章中,我们描述了三种创建独立的可执行JAR的方法:
- 组装非常适合简单的项目
- 当项目开始变得更加复杂并且您需要处理重复的文件时,请使用Shade
- 最后,对于Spring Boot项目,最好的选择是专用插件
可以在Github上以Maven格式找到此帖子的完整源代码。