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

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

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()方法。

有 3 个启动器子类(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 启动器清单

您需要指定一个适当的Launcher作为META-INF/MANIFEST.MFMain-Class属性。您应该在Start-Class属性中指定要启动的实际类(即,您编写的包含main方法的类)。

例如,以下是可执行 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

您无需在清单文件中指定Class-Path条目,Classpath 将从嵌套的 jar 中推导出。

E.3.2Exploded archives

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

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

E.4 属性启动器功能

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

Note

PropertiesLauncher支持从loader.properties以及application.properties(出于历史原因)加载属性。我们建议仅使用loader.properties,因为对application.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.systemBoolean 标志,指示应将所有属性添加到系统属性(默认为false)

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

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

  • loader.home中搜索loader.properties,然后在 Classpath 的根目录中搜索,然后在classpath:/BOOT-INF/classes中搜索。使用存在的第一个位置。

  • 只要未指定loader.config.locationloader.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时的 JVMClasspath)。

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

  • 属性的搜索 Sequences(可以在多个位置查找是有意义的)是 env vars,系统属性loader.properties,分解 Files 清单,Files 清单。

E.5 可执行 jar 限制

使用 Spring Boot Loader 打包的应用程序时,需要考虑许多限制。

E.5.1Zip 压缩

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

E.5.2 系统 ClassLoader

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

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

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