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的适当部分来加载特定的嵌套条目。我们不需要解压存档,也不需要将所有条目数据读入内存。
3. 启动可执行的Jars
org.springframework.boot.loader.launch.Launcher
类是一个特殊的引导类,用作可执行jar的主入口点。它是您jar文件中的实际Main-Class
,用于设置适当的ClassLoader
并最终调用您的main()
方法。
有三个启动器子类(JarLauncher
、WarLauncher
和PropertiesLauncher
)。它们的目的是从嵌套的jar文件或war文件中的目录(而不是显式在类路径上的文件)加载资源(.class
文件等)。在JarLauncher
和WarLauncher
的情况下,嵌套路径是固定的。JarLauncher
查找BOOT-INF/lib/
,WarLauncher
查找WEB-INF/lib/
和WEB-INF/lib-provided/
。如果需要更多,可以在这些位置添加额外的jar包。
PropertiesLauncher
默认在您的应用程序存档中的BOOT-INF/lib/
中查找。您可以通过设置名为LOADER_PATH
或loader.path
的环境变量或在loader.properties
中设置(这是一个逗号分隔的目录、存档或存档内目录的列表)来添加额外的位置。
3.1. 启动器清单
您需要在META-INF/MANIFEST.MF
的Main-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
)启用。以下表格描述了这些属性:
键 | 目的 |
---|---|
|
逗号分隔的类路径,例如 |
|
用于解析 |
|
主方法的默认参数(以空格分隔)。 |
|
要启动的主类的名称(例如, |
|
属性文件的名称(例如, |
|
属性文件的路径(例如, |
|
指示所有属性是否应添加到系统属性的布尔标志。默认为 |
当作为环境变量或清单条目指定时,应使用以下名称:
键 | 清单条目 | 环境变量 |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
构建插件在构建超级 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
总是使用系统类加载器。因此,您应考虑使用不同的日志记录实现。