本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。

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

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

1. 嵌套的JAR

Java没有提供任何标准的方法来加载嵌套的jar文件(也就是说,jar文件本身包含在jar中)。 如果你需要发布一个不需要解压就可以从命令行中运行的独立的应用程序,这可能是个问题。

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

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

Application class 应放在嵌套的 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/ 目录下的额外索引文件。 classpath.idx 文件可以为jars和war提供,它提供了jars应该被添加到classpath的顺序。 layers.idx 文件只能用于jar,它允许将jar分割成逻辑层,用于创建 Docker/OCI 镜像。

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

1.4. Classpath 索引(Index)

classpath索引文件可以在 BOOT-INF/classpath.idx 中提供。 它提供了一个jar名称(包括目录)的列表,按照它们应该被添加到classpath中的顺序。 每一行必须以破折号空格( "-·" )开始,名称必须在双引号中。

例如,给定以下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的 “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

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

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

2.1. 与标准Java “JarFile” 兼容

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

3. 启动可执行的Jar

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

有三个启动器(Launcher)子类(JarLauncher, WarLauncher, 和 PropertiesLauncher)。 它们的目的是从嵌套的jar文件或目录中的war文件(而不是明确在classpath上的文件)加载资源(.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 的环境变量来添加额外的位置(这是一个逗号分隔的目录、存档或存档中的目录列表)。

3.1. Launcher 清单(Manifest)

你需要在 META-INF/MANIFEST.MF 的 Main-Class 属性中指定一个合适的 Launcher。 你想启动的实际类(也就是包含 main 方法的类)应该在 Start-Class 属性中指定。

下面的例子显示了一个典型的 MANIFEST.MF,用于一个可执行的jar文件。

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 条目。 classpath是由嵌套的jar推导出来的。

4. PropertiesLauncher 的功能

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

Key 目的

loader.path

逗号分隔的Classpath,例如 lib,${HOME}/app/lib。 前面的条目优先,就像 javac 命令行中的普通 classpath 一样。

loader.home

用于解决 loader.path 中的相对路径。 例如,给定 loader.path=lib,那么 ${loader.home}/lib 就是一个classpath位置(连同该目录下的所有jar文件)。 这个属性也用于定位 loader.properties 文件,如下面的例子 /opt/app 它默认为 ${user.dir}。

loader.args

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

loader.main

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

loader.config.name

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

loader.config.location

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

loader.system

Boolean flag,表示所有属性都应该被添加到系统属性(System properties)中。 它的默认值是 false。

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

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.properties 在 loader.home 中搜索,然后在classpath的根部搜索,再在 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是 PropertiesLauncher 启动时的JVM classpath)。

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

  • 属性的搜索顺序是环境变量、系统属性、loader.properties、exploded archive和归档清单(archive manifest)(如果在多个地方搜索有意义)。

5. 可执行Jar的限制

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

  • zip 压缩: 嵌套jar的 ZipEntry 必须通过使用 ZipEntry.STORED 方法保存。 这是必须的,这样我们就可以直接寻求嵌套jar中的个别内容。 嵌套jar文件本身的内容仍然可以被压缩,就像外部jar中的任何其他条目一样。

  • System classLoader:启动的应用程序在加载类时应该使用 Thread.getContextClassLoader()(大多数库和框架默认都这样做)。 试图用 ClassLoader.getSystemClassLoader() 来加载嵌套的jar类是失败的。 java.util.Logging 总是使用系统类加载器。 由于这个原因,你应该考虑使用不同的日志实现。

6. 其他 “Single Jar” 解决方案

如果前面的限制意味着你不能使用Spring Boot Loader,可以考虑以下替代方案。