打包可执行档案

该插件可以创建可执行档案(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 目录中,从而不会与外部容器自身的类发生冲突。

相较于 Gradle 的 compileOnly 配置,providedRuntime 更受青睐,因为除了其他限制外,compileOnly 依赖项不在测试类路径上,因此任何基于 Web 的集成测试都将失败。

打包可执行和普通档案

默认情况下,当配置 bootJarbootWar 任务时,jarwar 任务会配置使用 plain 作为其档案分类器的约定。这确保 bootJarjar(或 bootWarwar)具有不同的输出位置,允许同时构建可执行档案和普通档案。

如果你偏好可执行档案使用分类器而非普通档案使用,请按照以下示例为 jarbootJar 任务配置分类器

  • 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
}
创建 Native Image 时不要禁用 jar 任务。详见 #33238

配置可执行档案打包

BootJarBootWar 任务分别是 Gradle 的 JarWar 任务的子类。因此,打包 jar 或 war 时所有可用的标准配置选项在打包可执行 jar 或 war 时也可用。此外,还提供了一些针对可执行 jar 和 war 的特定配置选项。

配置主类

默认情况下,可执行档案的主类将通过在主源集的输出中查找具有 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 插件,其 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 配置中声明的依赖项,请配置其任务的类路径以包含该配置,如下例所示(适用于 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")
}

为了获得更多控制,还可以使用 closure。closure 会被传入一个 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/classesBOOT-INF/lib 中。类似地,bootWar 构建的档案包含应用程序的类位于 WEB-INF/classes 中,依赖项位于 WEB-INF/libWEB-INF/lib-provided 中。对于需要从 jar 内容构建 docker 镜像的情况,进一步分离这些目录非常有用,这样它们可以写入不同的层。

分层 jar 使用与普通 boot 打包 jar 相同的布局,但包含一个额外的元数据文件,用于描述每个层。

默认情况下,定义了以下层

  • dependencies 用于版本不包含 SNAPSHOT 的任何非项目依赖项。

  • spring-boot-loader 用于 jar 加载器类。

  • snapshot-dependencies 用于版本包含 SNAPSHOT 的任何非项目依赖项。

  • application 用于项目依赖项、应用程序类和资源。

层的顺序很重要,因为它决定了当应用程序部分发生变化时,之前的层有多大可能被缓存。默认顺序是 dependenciesspring-boot-loadersnapshot-dependenciesapplication。最不容易更改的内容应该首先添加,然后是更容易更改的层。

要禁用此功能,可以按照以下方式进行

  • Groovy

  • Kotlin

tasks.named("bootJar") {
	layered {
		enabled = false
	}
}
tasks.named<BootJar>("bootJar") {
	layered {
		enabled.set(false)
	}
}

创建分层 jar 或 war 时,spring-boot-jarmode-tools jar 将作为依赖项添加到你的档案中。通过类路径上的此 jar,你可以以特殊模式启动应用程序,该模式允许引导代码运行与你的应用程序完全不同的内容,例如,提取层的内容。如果你希望排除此依赖项,可以按照以下方式进行

  • 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 closure 定义了应用程序类和资源如何分层。

  • dependencies closure 定义了依赖项如何分层。

  • layerOrder 方法定义了层写入的顺序。

嵌套的 intoLayer closures 用于 applicationdependencies 部分,以声明某个层的内容。这些 closures 按其定义的顺序从上到下进行评估。任何未被早期 intoLayer closure 声明的内容对后续 closure 仍然可用。

intoLayer closure 使用嵌套的 includeexclude 调用声明内容。application closure 使用 Ant 风格路径匹配作为 include/exclude 参数。dependencies 部分使用 group:artifact[:version] 模式。它还提供了 includeProjectDependencies()excludeProjectDependencies() 方法,可用于包含或排除项目依赖项。

如果没有 include 调用,则考虑所有内容(未被早期 closure 声明的内容)。

如果没有 exclude 调用,则不应用任何排除。

查看上面示例中的 dependencies closure,我们可以看到第一个 intoLayer 将为 application 层声明所有项目依赖项。下一个 intoLayer 将为 snapshot-dependencies 层声明所有 SNAPSHOT 依赖项。第三个也是最后一个 intoLayer 将为 dependencies 层声明剩余的内容(在此情况下,任何不是项目依赖项或 SNAPSHOT 的依赖项)。

application closure 有类似的规则。首先为 spring-boot-loader 层声明 org/springframework/boot/loader/** 内容。然后为 application 层声明任何剩余的类和资源。

添加 intoLayer closures 的顺序通常与写入层的顺序不同。因此,必须始终调用 layerOrder 方法,并且*必须*涵盖所有由 intoLayer 调用引用的层。