打包可执行归档文件
该插件可以创建可执行的归档文件(jar 文件和 war 文件),其中包含应用程序的所有依赖项,然后可以通过 java -jar 运行。
打包可执行 JAR 文件
可执行 JAR 可以通过 bootJar 任务构建。当应用 java 插件时,该任务会自动创建,并且是 BootJar 的一个实例。assemble 任务会自动配置为依赖于 bootJar 任务,因此运行 assemble(或 build)也会执行 bootJar 任务。
打包可执行的 WAR 文件
可执行的 WAR 文件可以使用 bootWar 任务构建。当应用 war 插件时,该任务会自动创建,并且是 BootWar 的一个实例。assemble 任务会自动配置为依赖于 bootWar 任务,因此运行 assemble(或 build)也会执行 bootWar 任务。
打包可执行和可部署的 War 包
可以将 war 文件打包,使其能够通过 java -jar 执行并部署到外部容器中。为此,应将内嵌的 servlet 容器依赖项添加到 providedRuntime 配置中,例如:
- Groovy
- Kotlin
dependencies {
implementation('org.springframework.boot:spring-boot-starter-web')
providedRuntime('org.springframework.boot:spring-boot-starter-tomcat')
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
providedRuntime("org.springframework.boot:spring-boot-starter-tomcat")
}
这确保了它们被打包到 war 文件的 WEB-INF/lib-provided 目录中,从而不会与外部容器自身的类发生冲突。
providedRuntime 优于 Gradle 的 compileOnly 配置,因为 compileOnly 存在一些限制,例如 compileOnly 依赖项不会包含在测试类路径中,因此任何基于 Web 的集成测试都会失败。
打包可执行文件和普通归档文件
默认情况下,当配置 bootJar 或 bootWar 任务时,jar 或 war 任务会被配置为使用 plain 作为其归档分类器(archive classifier)的约定。这确保了 bootJar 与 jar,或 bootWar 与 war 具有不同的输出位置,从而允许可执行归档和普通归档同时构建。
如果你希望可执行归档文件(而非普通归档文件)使用一个 classifier,请按以下示例为 jar 和 bootJar 任务配置 classifiers:
- Groovy
- Kotlin
tasks.named("bootJar") {
archiveClassifier = 'boot'
}
tasks.named("jar") {
archiveClassifier = ''
}
tasks.named<BootJar>("bootJar") {
archiveClassifier.set("boot")
}
tasks.named<Jar>("jar") {
archiveClassifier.set("")
}
或者,如果你不希望构建普通的归档文件,可以像下面针对 jar 任务的示例那样,禁用其对应的任务:
- Groovy
- Kotlin
tasks.named("jar") {
enabled = false
}
tasks.named<Jar>("jar") {
enabled = false
}
在创建原生镜像时,不要禁用 jar 任务。详情请参见 #33238。
配置可执行归档打包
配置主类
默认情况下,可执行归档文件的主类将通过在主源集的输出中查找具有 public static void main(String[]) 方法的类来自动配置。
也可以使用任务的 mainClass 属性显式配置主类:
- Groovy
- Kotlin
tasks.named("bootJar") {
mainClass = 'com.example.ExampleApplication'
}
tasks.named<BootJar>("bootJar") {
mainClass.set("com.example.ExampleApplication")
}
或者,可以使用 Spring Boot DSL 的 mainClass 属性在项目范围内配置主类名称:
- Groovy
- Kotlin
springBoot {
mainClass = 'com.example.ExampleApplication'
}
springBoot {
mainClass.set("com.example.ExampleApplication")
}
如果已应用 application plugin,则必须配置其 mainClass 属性,并可用于相同的目的:
- Groovy
- Kotlin
application {
mainClass = 'com.example.ExampleApplication'
}
application {
mainClass.set("com.example.ExampleApplication")
}
最后,可以在任务的 manifest 中配置 Start-Class 属性:
- Groovy
- Kotlin
tasks.named("bootJar") {
manifest {
attributes 'Start-Class': 'com.example.ExampleApplication'
}
}
tasks.named<BootJar>("bootJar") {
manifest {
attributes("Start-Class" to "com.example.ExampleApplication")
}
}
如果主类是用 Kotlin 编写的,则应使用生成的 Java 类的名称。默认情况下,该名称是在 Kotlin 类名后加上 Kt 后缀。例如,ExampleApplication 会变成 ExampleApplicationKt。如果使用 @JvmName 指定了其他名称,则应使用该名称。
包含仅用于开发的依赖项
默认情况下,所有在 developmentOnly 配置中声明的依赖项都会被排除在可执行的 jar 或 war 之外。
如果你想在你的归档文件中包含 developmentOnly 配置中声明的依赖项,请配置其任务的 classpath 以包含该配置,如下例所示(针对 bootWar 任务):
- Groovy
- Kotlin
tasks.named("bootWar") {
classpath configurations.developmentOnly
}
tasks.named<BootWar>("bootWar") {
classpath(configurations["developmentOnly"])
}
配置需要解包的库
大多数库在嵌套于可执行归档文件中时可以直接使用,但某些库可能会出现问题。例如,JRuby 自带了其自身的嵌套 JAR 支持,该支持假定 jruby-complete.jar 始终可直接在文件系统上访问。
为处理任何有问题的库,可将可执行归档文件配置为在运行时将特定的嵌套 JAR 解压到临时目录。可以使用 Ant 风格的模式来标识需要解压的库,这些模式会与源 JAR 文件的绝对路径进行匹配:
- Groovy
- Kotlin
tasks.named("bootJar") {
requiresUnpack '**/jruby-complete-*.jar'
}
tasks.named<BootJar>("bootJar") {
requiresUnpack("**/jruby-complete-*.jar")
}
为了更精细的控制,也可以使用闭包。该闭包接收一个 FileTreeElement 作为参数,并应返回一个 boolean 值,以指示是否需要解包。
使归档文件完全可执行
Spring Boot 提供了对完全可执行归档文件的支持。通过在归档文件前添加一段知道如何启动应用程序的 shell 脚本,即可使其成为完全可执行的。在类 Unix 平台上,该启动脚本允许直接像运行其他可执行文件一样运行该归档文件,或者将其安装为服务。
目前,某些工具不支持此格式,因此你可能无法始终使用此技术。例如,jar -xf 在解压一个被设置为完全可执行的 JAR 或 WAR 文件时可能会静默失败。建议仅在你打算直接执行该文件时才启用此选项,而不是通过 java -jar 运行或将其部署到 Servlet 容器中。
要使用此功能,必须启用启动脚本的包含:
- Groovy
- Kotlin
tasks.named("bootJar") {
launchScript()
}
tasks.named<BootJar>("bootJar") {
launchScript()
}
这将把 Spring Boot 的默认启动脚本添加到归档文件中。默认启动脚本包含多个具有合理默认值的属性。这些属性的值可以通过 properties 属性进行自定义:
- Groovy
- Kotlin
tasks.named("bootJar") {
launchScript {
properties 'logFilename': 'example-app.log'
}
}
tasks.named<BootJar>("bootJar") {
launchScript {
properties(mapOf("logFilename" to "example-app.log"))
}
}
如果默认的启动脚本无法满足你的需求,可以使用 script 属性来提供一个自定义的启动脚本:
- Groovy
- Kotlin
tasks.named("bootJar") {
launchScript {
script = file('src/custom.script')
}
}
tasks.named<BootJar>("bootJar") {
launchScript {
script = file("src/custom.script")
}
}
使用 PropertiesLauncher
要使用 PropertiesLauncher 启动可执行的 jar 或 war 文件,请配置任务的 manifest 以设置 Main-Class 属性:
- Groovy
- Kotlin
tasks.named("bootWar") {
manifest {
attributes 'Main-Class': 'org.springframework.boot.loader.launch.PropertiesLauncher'
}
}
tasks.named<BootWar>("bootWar") {
manifest {
attributes("Main-Class" to "org.springframework.boot.loader.launch.PropertiesLauncher")
}
}
打包分层 Jar 或 War
默认情况下,bootJar 任务会构建一个归档文件,其中应用程序的类和依赖项分别位于 BOOT-INF/classes 和 BOOT-INF/lib 中。类似地,bootWar 任务会构建一个归档文件,其中应用程序的类位于 WEB-INF/classes 中,依赖项位于 WEB-INF/lib 和 WEB-INF/lib-provided 中。在需要从 JAR 文件内容构建 Docker 镜像的情况下,如果能进一步分离这些目录,以便将它们写入不同的层,将会非常有用。
分层 JAR 使用与常规的 boot 打包 JAR 相同的布局,但包含一个额外的元数据文件,用于描述每一层。
默认情况下,定义了以下层:
-
dependencies用于任何版本号中不包含SNAPSHOT的非项目依赖。 -
spring-boot-loader用于 jar 加载器类。 -
snapshot-dependencies用于任何版本号中包含SNAPSHOT的非项目依赖。 -
application用于项目依赖、应用程序类和资源。
层的顺序很重要,因为它决定了当应用程序的一部分发生变化时,先前的层有多大可能被缓存。默认顺序为 dependencies、spring-boot-loader、snapshot-dependencies、application。最不可能发生变化的内容应最先添加,随后是更可能发生变更的层。
要禁用此功能,您可以按以下方式操作:
- Groovy
- Kotlin
tasks.named("bootJar") {
layered {
enabled = false
}
}
tasks.named<BootJar>("bootJar") {
layered {
enabled.set(false)
}
}
当创建分层的 jar 或 war 时,spring-boot-jarmode-tools jar 将作为依赖项添加到你的归档文件中。当该 jar 位于 classpath 上时,你可以以一种特殊模式启动应用程序,该模式允许引导代码运行与你的应用程序完全不同的内容,例如,提取各层的内容。如果你希望排除此依赖项,可以按以下方式操作:
- Groovy
- Kotlin
tasks.named("bootJar") {
includeTools = false
}
tasks.named<BootJar>("bootJar") {
includeTools.set(false)
}
自定义层配置
根据你的应用,你可能需要调整图层的创建方式并添加新的图层。
这可以通过配置来实现,该配置描述了如何将 jar 或 war 分割为多个层,以及这些层的顺序。以下示例展示了如何显式地定义上述默认的顺序:
- Groovy
- Kotlin
tasks.named("bootJar") {
layered {
application {
intoLayer("spring-boot-loader") {
include "org/springframework/boot/loader/**"
}
intoLayer("application")
}
dependencies {
intoLayer("application") {
includeProjectDependencies()
}
intoLayer("snapshot-dependencies") {
include "*:*:*SNAPSHOT"
}
intoLayer("dependencies")
}
layerOrder = ["dependencies", "spring-boot-loader", "snapshot-dependencies", "application"]
}
}
tasks.named<BootJar>("bootJar") {
layered {
application {
intoLayer("spring-boot-loader") {
include("org/springframework/boot/loader/**")
}
intoLayer("application")
}
dependencies {
intoLayer("application") {
includeProjectDependencies()
}
intoLayer("snapshot-dependencies") {
include("*:*:*SNAPSHOT")
}
intoLayer("dependencies")
}
layerOrder.set(listOf("dependencies", "spring-boot-loader", "snapshot-dependencies", "application"))
}
}
layered DSL 通过三个部分进行定义:
-
application闭包定义了应用程序类和资源应该如何分层。 -
dependencies闭包定义了依赖项应该如何分层。 -
layerOrder方法定义了各层的写入顺序。
嵌套的 intoLayer 闭包用于 application 和 dependencies 部分中,以声明某一层的内容。这些闭包按照定义的顺序从上到下依次求值。任何未被前面 intoLayer 闭包声明的内容,将保持可用状态,供后续的闭包考虑使用。
intoLayer 闭包通过嵌套的 include 和 exclude 调用来声明内容。application 闭包对 include/exclude 参数使用 Ant 风格的路径匹配。dependencies 部分使用 group:artifact[:version] 模式。它还提供了 includeProjectDependencies() 和 excludeProjectDependencies() 方法,可用于包含或排除项目依赖项。
如果没有调用 include,则所有内容(未被先前的闭包声明的部分)都会被考虑。
如果没有调用 exclude,则不会应用任何排除规则。
查看上面示例中的 dependencies 闭包,我们可以看到第一个 intoLayer 会将所有项目依赖分配给 application 层。下一个 intoLayer 会将所有 SNAPSHOT 依赖分配给 snapshot-dependencies 层。第三个也是最后一个 intoLayer 会将剩余的所有内容(在本例中,即既不是项目依赖也不是 SNAPSHOT 的依赖)分配给 dependencies 层。
application 闭包具有类似的规则。首先将 org/springframework/boot/loader/** 内容分配给 spring-boot-loader 层,然后将剩余的任何类和资源分配给 application 层。
intoLayer 闭包的添加顺序通常与图层的书写顺序不同。因此,必须始终调用 layerOrder 方法,并且该方法 必须 覆盖所有 intoLayer 调用中引用的图层。