附录 E.可执行 jar 格式

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

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

E.1 嵌套 JAR

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

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

E.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目录中。

E.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中。

E.2 Spring Boot 的“ JarFile”类

用于支持加载嵌套 jar 的核心类是org.springframework.boot.loader.jar.JarFile。它使您可以从标准 jar 文件或嵌套的子 jar 数据加载 jar 内容。首次加载时,每个JarEntry的位置都 Map 到外部 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.class3980的位置上。

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

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

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

E.3 启动可执行 jar

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

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

E.3.1 启动 Lists

您需要指定一个适当的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

Note

您无需在 Lists 文件中指定Class-Path个条目。Classpath 是从嵌套的 jar 中推导出来的。

E.3.2 Exploded archives

某些 PaaS 实施可能选择在运行之前解压缩存档。例如,Cloud Foundry 以这种方式运行。您可以通过启动适当的启动器来运行解压缩的存档,如下所示:

$ unzip -q myapp.jar
$ java org.springframework.boot.loader.JarLauncher

E.4 属性启动器功能

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

KeyPurpose
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.argsmain 方法的默认参数(以空格分隔)。
loader.main要启动的主类的名称(例如com.app.Application)。
loader.config.name属性文件的名称(例如launcher),默认为loader
loader.config.location属性文件的路径(例如classpath:loader.properties)。默认为loader.properties
loader.system布尔值标志,指示应将所有属性添加到系统属性。默认为false

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

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

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

以下规则适用于使用PropertiesLauncher

  • loader.home中搜索loader.properties,然后在 Classpath 的根目录中搜索,然后在classpath:/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的位置(用于搜索后者的 Classpath 是启动PropertiesLauncher时的 JVM Classpath)。

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

  • 属性(在多个位置中有意义的查找)的搜索 Sequences 是环境变量,系统属性loader.properties,分解的 FilesLists 和 FilesLists。

E.5 可执行 Jar 限制

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

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

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

E.6 替代独立 Jar 包解决方案

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