20220606 Appendix - 5. Executable Jars

前言

文档地址

spring-boot-loader 模块让 Spring Boot 支持可执行的 jar 和 war 文件。如果您使用 Maven 插件或 Gradle 插件,可执行 jar 会自动生成,您通常不需要了解它们的工作原理。

如果您需要从不同的构建系统创建可执行 jar,或者您只是对底层技术感到好奇,本附录提供了一些背景知识。

1. Nested JARs

Java 不提供任何标准方法来加载嵌套的 jar 文件(即,本身包含在 jar 中的 jar 文件)。如果您需要分发可以从命令行运行而无需解包的独立应用程序,这可能会出现问题。

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

1.1. 可执行 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 目录中。

1.2. 可执行的 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

1.3. Index Files

Spring Boot Loader 兼容的 jar 和 war 文件可以在 BOOT-INF/ 目录下包含其他索引文件。可以为 jars 和 wars 提供一个 classpath.idx 文件,它提供了将 jars 添加到类路径中的顺序。layers.idx 文件只能用于 jar,它允许将 jar 拆分为逻辑层以创建 Docker/OCI 映像。

索引文件遵循 YAML 兼容的语法,因此它们可以很容易地被第三方工具解析。但是,这些文件不会在内部被解析为 YAML,它们必须完全按照下面描述的格式编写才能使用。

1.4. 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"

1.5. 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/"

2. Spring Boot’s “JarFile” Class

用于支持加载嵌套 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/BOOT-INF/classes 中的 0063 位置找到 A.class 。嵌套 jar 中的 B.class 实际上可以在 myapp.jar 中的 3452 位置找到,而 C.class 在位置 3980

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

2.1. Compatibility with the Standard Java “JarFile”

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

3. Launching Executable Jars

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

有三个启动器子类( JarLauncherWarLauncherPropertiesLauncher )。它们的目的是从目录中的嵌套 jar 文件或 war 文件加载资源(.class 文件等)(与那些显式在类路径上的文件相反)。在 JarLauncherWarLauncher 的情况下,嵌套路径是固定的。 JarLauncher 查找 BOOT-INF/lib/WarLauncher 查找 WEB-INF/lib/WEB-INF/lib-provided/ 。如果你想要更多,你可以在这些位置添加额外的 jar 。默认情况下,PropertiesLauncher 在您的应用程序存档中查找 BOOT-INF/lib/ 。您可以通过在 loader.properties 中设置一个名为 LOADER_PATHloader.path 的环境变量来添加其他位置(这是一个逗号分隔的目录、档案或档案中的目录的列表)。

3.1. Launcher Manifest

您需要指定适当的 Launcher 作为 META-INF/MANIFEST.MFMain-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

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

4. PropertiesLauncher Features

PropertiesLauncher 有一些可以通过外部属性(系统属性、环境变量、清单条目或 loader.properties )启用的特殊功能。下表描述了这些属性:

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

当指定为环境变量或清单条目时,应使用以下名称:

Key 清单条目 环境变量
loader.path Loader-Path LOADER_PATH
loader.home Loader-Home LOADER_HOME
loader.args Loader-Args LOADER_ARGS
loader.main Start-Class LOADER_MAIN
loader.config.location Loader-Config-Location LOADER_CONFIG_LOCATION
loader.system Loader-System LOADER_SYSTEM

构建 fat jar 时,构建插件会自动将 Main-Class 属性移动到 Start-Class 。如果您使用它,请使用 Main-Class 属性并省略 Start-Class 来指定要启动的类的名称。

以下规则适用于使用 PropertiesLauncher

  • loader.home 中搜索 loader.properties ,然后在类路径的根目录中搜索,然后在 classpath:/BOOT-INF/classes 中搜索。使用具有该名称的文件存在的第一个位置。
  • loader.home 是附加属性文件的目录位置(覆盖默认值),仅当未指定 loader.config.location 时。
  • loader.path 可以包含目录(递归扫描 jar 和 zip 文件)、存档路径、存档中扫描 jar 文件的目录(例如,dependencies.jar!/lib )或通配符模式(用于默认 JVM 行为)。存档路径可以相对于 loader.home 或文件系统中带有 jar:file: 前缀的任何位置。
  • loader.path(如果为空)默认为 BOOT-INF/lib(表示本地目录或嵌套目录,如果从存档运行)。因此,PropertiesLauncher 在没有提供额外配置时的行为与 JarLauncher 相同。
  • loader.path 不能用来配置 loader.properties 位置(后者用来搜索的 classpath 是 JVM 启动 PropertiesLauncher 时的 classpath )。
  • 占位符替换是从系统和环境变量以及使用前所有值的属性文件本身完成的。
  • 属性的搜索顺序(在多个位置查找是有意义的)是环境变量、系统属性、loader.properties 、展开的存档清单和存档清单。

5. 可执行 Jar 限制

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

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

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

6. 替代单罐解决方案

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

posted @ 2022-06-08 08:10  流星<。)#)))≦  阅读(38)  评论(0编辑  收藏  举报