Skip to content

Appendix E: 可执行 Jar 格式

spring-boot-loader 模块使 Spring Boot 支持可执行的 jar 和 war 文件. 如果使用 Maven 插件或 Gradle 插件,则会自动生成可执行的jar,通常不需要了解其工作方式的详细信息.

如果您需要从其他构建系统创建可执行 jar,或者您只是对基础技术感到好奇,则本附录提供了一些背景知识.

E.1. 嵌套 JARs

Java 没有提供任何标准的方式来装载嵌套的 jar 文件 (即,它们本身包含在jar中的jar文件) . 如果您需要分发一个自包含的应用程序,而该应用程序可以从命令行运行而无需解压缩,则可能会出现问题.

为了解决这个问题,许多开发人员使用 “shaded” jars. 将包含所有 jar 的所有类的 shaded 的 jar 打包到一个 “uber jar” 中. 带 shaded 的 jar 的问题在于,很难查看应用程序中实际包含哪些库. 如果在多个 jar 中使用相同的文件名 (但具有不同的内容) ,也可能会产生问题. Spring Boot 采用了另一种方法,实际上允许您直接嵌套jar.

可执行的 Jar 文件结构

与 Spring Boot Loader 兼容的 jar 文件的结构应采用以下方式:

example.jar
 |
 +-META-INF
 |  +-MANIFEST.MF
 +-org
 |  +-springframework
 |     +-boot
 |        +-loader
 |           +-<spring boot loader classes>
 +-BOOT-INF
    +-classes
    |  +-mycompany
    |     +-project
    |        +-YourClasses.class
    +-lib
       +-dependency1.jar
       +-dependency2.jar

应用程序类应放在嵌套的 BOOT-INF/classes 目录中. 依赖应放在嵌套的 BOOT-INF/lib 目录中.

可执行 War 文件结构

与 Spring Boot Loader 兼容的 war 文件的结构应采用以下方式:

example.war
 |
 +-META-INF
 |  +-MANIFEST.MF
 +-org
 |  +-springframework
 |     +-boot
 |        +-loader
 |           +-<spring boot loader classes>
 +-WEB-INF
    +-classes
    |  +-com
    |     +-mycompany
    |        +-project
    |           +-YourClasses.class
    +-lib
    |  +-dependency1.jar
    |  +-dependency2.jar
    +-lib-provided
       +-servlet-api.jar
       +-dependency3.jar

依赖应放在嵌套的 WEB-INF/lib 目录中. 在运行嵌入式程序时需要但在部署到传统Web容器时不需要的任何依赖都应放在 WEB-INF/lib-provided 的文件中.

文件索引

Spring Boot Loader-compatible 的 jar 和 war 可以在 BOOT-INF/ 目录下附加一个索引文件,可以同时为 jar 和 war 提供一个 classpath.idx 文件,他提供了将 jar 加载到类路径的顺序. layers.idx 文件只能用于 jar,它允许将 jar 进行逻辑分层,以创建 Docker/OCI 镜像

索引文件使用兼容 YAML 的语法,以便可以由第三方工具轻松解析. 但是,这些文件在内部没有作为 YAML 进行解析,因此必须按照以下所述的格式编写,才能使用.

Classpath Index

可以在 BOOT-INF/classpath.idx 中提供类路径索引文件. 它提供了 jar 名称列表 (包括目录) ,其顺序为应将其添加到类路径中. 每行必须以破折号 ("-·") 开头,并且名称必须用双引号引起来.

例如,给出以下 jar:

example.jar
 |
 +-META-INF
 |  +-...
 +-BOOT-INF
    +-classes
    |  +...
    +-lib
       +-dependency1.jar
       +-dependency2.jar

索引文件应为:

- "BOOT-INF/lib/dependency2.jar"
- "BOOT-INF/lib/dependency1.jar"
Layer Index(分层索引)

可以在 BOOT-INF/layers.idx 中提供分层索引文件. 它提供了每一层的列表以及应包含在其中的 jar. 按照应将其添加到 Docker/OCI 镜像的顺序来分层. 每一层的名称以带引号的字符串编写,前缀带有短划线空格 ("-·") 和后缀冒号(":") . 分层内容是用引号引起来的字符串的文件名或目录名,并带有空格短划线 ("··-·"). 目录名称以 / 结尾,而文件名则没有. 使用目录名称时,意味着该目录内的所有文件都在同一层.

分层索引的典型示例是:

- "dependencies":
  - "BOOT-INF/lib/dependency1.jar"
  - "BOOT-INF/lib/dependency2.jar"
- "application":
  - "BOOT-INF/classes/"
  - "META-INF/"

E.2. Spring Boot “JarFile” 类

用于支持加载嵌套 jar 的核心类是 org.springframework.boot.loader.jar.JarFile. 它使您可以从标准 jar 文件或嵌套的子 jar 数据加载 jar 内容. 首次加载时,每个 JarEntry 的位置都映射到外部 jar 的物理文件偏移,如以下示例所示:

myapp.jar
+-------------------+-------------------------+
| /BOOT-INF/classes | /BOOT-INF/lib/mylib.jar |
|+-----------------+||+-----------+----------+|
||     A.class      |||  B.class  |  C.class ||
|+-----------------+||+-----------+----------+|
+-------------------+-------------------------+
 ^                    ^           ^
 0063                 3452        3980

前面的示例显示了如何在 myapp.jar 的 0063 置的 /BOOT-INF/classes 中找到 A.class. 嵌套jar的 B.class 实际上可以在 myapp.jar 的 3452 位置中找到,而 C.class`是 在位置 `3980.

有了这些信息,我们可以通过查找外部 jar 的适当部分来加载特定的嵌套条目. 我们不需要解压缩归档文件,也不需要将所有条目数据读入内存.

与标准Java “JarFile” 的兼容性

Spring Boot Loader 努力保持与现有代码和库的兼容性. org.springframework.boot.loader.jar.JarFile从java.util.jar.JarFile 扩展而来,应该可以作为替代产品. getURL() 方法返回一个 URL, 该 URL 打开一个与 java.net.JarURLConnection 兼容的连接,并且可以与 Java 的 URLClassLoader 一起使用.

E.3. 运行可执行 Jars

org.springframework.boot.loader.Launcher 类是特殊的引导程序类,用作可执行 jar 的主要入口点. 它是 jar 文件中的实际 Main-Class,用于设置适当的 URLClassLoader 并最终调用 main() 方法.

有三个启动器子类 (JarLauncher,WarLauncher 和 PropertiesLauncher) . 它们的目的是从目录中的嵌套 jar 文件或 war 文件 (而不是在类路径中显式的文件) 加载资源 (.class 文件等) . 对于 JarLauncher 和 WarLauncher,嵌套路径是固定的. JarLauncher 位于 BOOT-INF/lib/ 中, 而 WarLauncher 位于 WEB-INF/lib/ 和 WEB-INF/lib-provided/ 中. 如果需要,可以在这些位置添加额外的 jar. 默认情况下,PropertiesLauncher 在您的应用程序存档中的 BOOT-INF/lib/ 中查找. 您可以通过在 loader.properties (这是目录,归档文件或归档文件中的目录的逗号分隔列表) 中设置一个称为 LOADER_PATH 或 loader.path 的环境变量来添加其他位置.

运行 Manifest

您需要指定一个适当的 Launcher 作为 META-INF/MANIFEST.MF 的 Main-Class 属性. 您要启动的实际类 (即包含 main 方法的类) 应在 Start-Class 属性中指定.

下面的示例显示了一个可执行jar文件的典型 MANIFEST.MF:

Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.mycompany.project.MyApplication

如果是 war 文件,则如下

Main-Class: org.springframework.boot.loader.WarLauncher
Start-Class: com.mycompany.project.MyApplication

TIP

您无需在清单文件中指定 Class-Path 条目. 类路径是从嵌套的 jar 中推导出来的.

E.4. PropertiesLauncher 特性

PropertiesLauncher 具有一些可以通过外部属性 (系统属性,环境变量,manifest entries 或 loader.properties) 启用的特殊功能. 下表描述了这些属性:

KeyPurpose
loader.path逗号分隔的类路径,例如 lib,${HOME}/app/lib. 较早的条目具有优先权,就像 javac 命令行上的常规 -classpath 一样. .
loader.home用于解析 loader.path 中的相对路径. 例如,给定 loader.path=lib,,则 ${loader.home}/lib 是类路径位置 (以及该目录中的所有 jar 文件) . 此属性还用于查找 loader.properties 文件,如以下示例 /opt/app 所示. 它默认为 ${user.dir}.
loader.argsmain 方法的默认参数 (以空格分隔) .
loader.main要启动的主类的名称 (例如 com.app.Application) .
loader.config.name属性文件的名称 (例如,launcher) . 默认为 loader
loader.config.location属性文件的路径 (例如,classpath:loader.properties) . 默认为 loader.properties. )
loader.system布尔值标志,指示应将所有属性添加到系统属性. 默认为 false.

当指定为环境变量或 manifest 时,应使用以下名称:

KeyManifest entryEnvironment variable
loader.pathLoader-PathLOADER_PATH
loader.homeLoader-HomeLOADER_HOME
loader.argsLoader-ArgsLOADER_ARGS
loader.mainStart-ClassLOADER_MAIN
loader.config.locationLoader-Config-LocationLOADER_CONFIG_LOCATION
loader.systemLoader-SystemLOADER_SYSTEM

TIP

构建 fat jar 时,构建插件会自动将 Main-Class 属性移动到 Start-Class. 如果使用该名称,请使用 Main-Class 属性指定要启动的类的名称,而忽略 Start-Class.

以下规则适用于使用 PropertiesLauncher:

  • 在 loader.home 中搜索 loader.properties,然后在类路径的根目录中搜索,然后在类路径: /BOOT-INF/classes 中搜索. 使用具有该名称的文件的第一个位置.

  • 仅当未指定 loader.config.location 时,loader.home 是其他属性文件的目录位置 (覆盖默认值) .

  • loader.path 可以包含目录 (对 jar 和 zip 文件进行递归扫描) ,存档路径,存档文件中的对 jar 文件进行扫描的目录 (例如,dependencies.jar!/lib) 或通配符模式 (对于 默认 JVM 行为) . 归档路径可以相对于 loader.home 或文件系统中任何带有 jar:file: 前缀的位置.

  • loader.path (如果为空) 默认为 BOOT-INF/lib (表示本地目录,如果从归档文件运行,则为嵌套目录) . 因此,如果未提供其他配置,则 PropertiesLauncher 的行为与 JarLauncher 相同.

  • loader.path 不能用于配置 loader.properties 的位置 (用于启动后者的类路径是启动 PropertiesLauncher 时的JVM类路径) .

  • 占位符的替换是使用系统变量和环境变量以及属性文件本身的所有值完成的,然后再使用.

  • 属性 (在多个位置中有意义的查找) 的搜索顺序是环境变量,系统属性,loader.properties,暴露的存档清单和存档清单.

E.5. 可执行 Jar 限制

使用 Spring Boot Loader 打包的应用程序时,需要考虑以下限制:

  • Zip 压缩: 必须使用 ZipEntry.STORED 方法保存嵌套jar的 ZipEntry. 这是必需的,以便我们可以直接在嵌套 jar 中查找单个内容. 嵌套 jar 文件本身的内容仍然可以压缩,外部 jar 中的任何其他条目也可以压缩.

  • System classLoader: 启动的应用程序在加载类时应使用 Thread.getContextClassLoader() (默认情况下,大多数库和框架都使用 Thread.getContextClassLoader()) . 尝试使用 ClassLoader.getSystemClassLoader() 加载嵌套jar类失败. java.util.Logging 始终使用系统类加载器. 因此,您应该考虑使用其他日志记录实现.

E.6. 替代 单一 Jar 方案

如果上述限制意味着您不能使用 Spring Boot Loader,请考虑以下替代方法: