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

如果您需要从不同的构建系统创建可执行的jar文件,或者只是对底层技术感兴趣,本附录提供了一些背景信息。

1. 嵌套的JAR文件

Java没有提供任何标准方法来加载嵌套的jar文件(即,jar文件本身包含在另一个jar文件中)。如果您需要分发一个可以在命令行上运行而无需解压的自包含应用程序,这可能会有问题。

为了解决这个问题,许多开发人员使用“shaded” jars。一个shaded jar将所有jar中的所有类打包到一个“uber jar”中。使用shaded jars的问题在于很难看出实际上在您的应用程序中有哪些库。如果多个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. 索引文件

符合Spring Boot Loader的jar和war归档文件可以在BOOT-INF/目录下包含额外的索引文件。可以为jar和war提供一个classpath.idx文件,它提供了应该添加到类路径的jar的顺序。layers.idx文件只能用于jar,它允许将jar拆分为逻辑层,以用于Docker/OCI镜像创建。

索引文件遵循YAML兼容的语法,以便可以轻松地被第三方工具解析。然而,这些文件不会在内部解析为YAML,必须按照下面描述的格式精确编写才能使用。

1.4. 类路径索引

类路径索引文件可以在BOOT-INF/classpath.idx中提供。通常,它会被Spring Boot的Maven和Gradle构建插件自动生成。它提供了应该按顺序添加到类路径的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. 层索引

层索引文件可以在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的“NestedJarFile”类

用于支持加载嵌套jar的核心类是org.springframework.boot.loader.jar.NestedJarFile。它允许您从嵌套的子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中找到A.class,位置为0063。来自嵌套jar的B.class实际上可以在myapp.jar中的位置3452找到,C.class在位置3980

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

2.1. 与标准Java“JarFile”的兼容性

Spring Boot Loader致力于保持与现有代码和库的兼容性。org.springframework.boot.loader.jar.NestedJarFile扩展自java.util.jar.JarFile,应该可以作为一个可直接替换的组件。

支持形式为jar:nested:/path/myjar.jar/!BOOT-INF/lib/mylib.jar!/B.class的嵌套JAR URL,并打开与java.net.JarURLConnection兼容的连接。这些可以与Java的URLClassLoader一起使用。

3. 启动可执行的Jars

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

有三个启动器子类(JarLauncherWarLauncherPropertiesLauncher)。它们的目的是从嵌套的jar文件或war文件中的目录(而不是显式在类路径上的文件)加载资源(.class文件等)。在JarLauncherWarLauncher的情况下,嵌套路径是固定的。JarLauncher查找BOOT-INF/lib/WarLauncher查找WEB-INF/lib/WEB-INF/lib-provided/。如果需要更多,可以在这些位置添加额外的jar包。

PropertiesLauncher默认在您的应用程序存档中的BOOT-INF/lib/中查找。您可以通过设置名为LOADER_PATHloader.path的环境变量或在loader.properties中设置(这是一个逗号分隔的目录、存档或存档内目录的列表)来添加额外的位置。

3.1. 启动器清单

您需要在META-INF/MANIFEST.MFMain-Class属性中指定适当的Launcher。您希望启动的实际类(即包含main方法的类)应在Start-Class属性中指定。

以下示例显示了可执行jar文件的典型MANIFEST.MF

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

对于war文件,示例如下:

Main-Class: org.springframework.boot.loader.launch.WarLauncher
Start-Class: com.mycompany.project.MyApplication
您无需在清单文件中指定Class-Path条目。类路径是从嵌套的jar包中推断出来的。

4. PropertiesLauncher 功能

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

目的

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.args

主方法的默认参数(以空格分隔)。

loader.main

要启动的主类的名称(例如,com.app.Application)。

loader.config.name

属性文件的名称(例如,launcher)。默认为 loader

loader.config.location

属性文件的路径(例如,classpath:loader.properties)。默认为 loader.properties

loader.system

指示所有属性是否应添加到系统属性的布尔标志。默认为 false

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

清单条目 环境变量

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

构建插件在构建超级 jar 时会自动将 Main-Class 属性移动到 Start-Class。如果使用该功能,请使用 Main-Class 属性指定要启动的类的名称,并省略 Start-Class

使用 PropertiesLauncher 时适用以下规则:

  • loader.properties 首先在 loader.home 中搜索,然后在类路径的根目录中搜索,最后在 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 的位置(当启动 PropertiesLauncher 时,用于搜索后者的类路径是 JVM 类路径)。

  • 在使用之前,所有值都会从系统和环境变量以及属性文件本身进行占位符替换。

  • 属性的搜索顺序(在有意义的情况下查看多个位置)为环境变量、系统属性、loader.properties、解压缩的存档清单和存档清单。

5. 可执行 Jar 限制

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

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

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

6. 替代的单一 Jar 解决方案

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