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

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

1. 嵌套 JARs

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

为了解决这个问题,许多开发人员使用 “shaded” jars. 将包含所有jar的所有类的 shaded 的 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 的文件中.

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.jar0063 置的 /BOOT-INF/classes 中找到 A.class. 嵌套jar的 B.class 实际上可以在 myapp.jar3452 位置中找到,而 C.class`是 在位置 `3980.

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

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

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

3. 运行可执行 Jars

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

有三个启动器子类 (JarLauncher,WarLauncherPropertiesLauncher) . 它们的目的是从目录中的嵌套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. 运行 Manifest

您需要指定一个适当的启动器作为 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 特性

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

Key Purpose

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

main方法的默认参数 (以空格分隔) .

loader.main

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

loader.config.name

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

loader.config.location

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

loader.system

布尔值标志,指示应将所有属性添加到系统属性. 默认为 false.

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

Key Manifest entry Environment variable

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,然后在类路径的根目录中搜索,然后在类路径: /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,爆炸的存档清单和存档清单.

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 始终使用系统类加载器. 因此,您应该考虑使用其他日志记录实现.

6. 替代 单一 Jar 方案

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