打包可执行文件
在软件开发过程中,打包可执行文件是一个重要的步骤,它使得应用程序能够在不同的操作系统和环境中顺利运行。本文将介绍如何将应用程序打包为可执行文件,并确保其在不同平台上的兼容性。
1. 选择打包工具
首先,你需要选择一个适合的打包工具。常见的打包工具包括:
- PyInstaller:适用于 Python 应用程序,可以将 Python 脚本打包为独立的可执行文件。
- Electron Packager:适用于 Electron 应用程序,可以将基于 Electron 的应用打包为可执行文件。
- Maven:适用于 Java 应用程序,可以将 Java 项目打包为 JAR 文件。
2. 配置打包环境
在选择好打包工具后,你需要配置打包环境。这通常包括:
- 安装必要的依赖项。
- 设置打包脚本或配置文件。
- 确保所有资源文件和依赖库都包含在打包过程中。
3. 执行打包命令
配置好环境后,你可以执行打包命令来生成可执行文件。例如,使用 PyInstaller 打包 Python 脚本的命令如下:
pyinstaller --onefile your_script.py
这将生成一个独立的可执行文件,可以在目标系统上直接运行。
4. 测试可执行文件
生成可执行文件后,务必进行测试以确保其在不同环境下的兼容性和稳定性。你可以在以下环境中进行测试:
- 不同的操作系统(如 Windows、macOS、Linux)。
- 不同版本的运行时环境。
- 不同的硬件配置。
5. 分发可执行文件
最后,你可以将打包好的可执行文件分发给用户。分发方式可以包括:
- 通过官方网站提供下载链接。
- 使用软件包管理器进行分发。
- 通过云存储服务分享。
通过以上步骤,你可以成功地将应用程序打包为可执行文件,并确保其在各种环境中的正常运行。
该插件可以创建包含所有应用程序依赖项的可执行归档文件(jar
文件和 war
文件),然后可以使用 java -jar
运行这些文件。
打包可执行 JAR 文件
可执行的 JAR 文件可以通过 bootJar
任务来构建。当应用了 java
插件时,该任务会自动创建,并且是 BootJar 的一个实例。assemble
任务会自动配置为依赖于 bootJar
任务,因此运行 assemble
(或 build
)时也会运行 bootJar
任务。
打包可执行的 Wars
可以通过 bootWar
任务来构建可执行的 war 文件。当应用了 war
插件时,该任务会自动创建,并且是 BootWar 的一个实例。assemble
任务会自动配置为依赖于 bootWar
任务,因此运行 assemble
(或 build
)时也会同时运行 bootWar
任务。
打包可执行和可部署的 Wars
一个 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
存在一些限制,例如其依赖项不会出现在测试类路径中,因此任何基于 Web 的集成测试都会失败。
打包可执行文件和普通归档文件
默认情况下,当配置了 bootJar
或 bootWar
任务时,jar
或 war
任务会被配置为使用 plain
作为其归档分类器的约定。这确保了 bootJar
和 jar
或 bootWar
和 war
有不同的输出位置,从而允许同时构建可执行归档文件和普通归档文件。
如果您希望可执行归档文件(而不是普通归档文件)使用分类器,请为 jar
和 bootJar
任务配置分类器,如下例所示:
- 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 插件,则必须配置其 mainClass
属性,并且可以用于相同的目的:
- Groovy
- Kotlin
application {
mainClass = 'com.example.ExampleApplication'
}
application {
mainClass.set("com.example.ExampleApplication")
}
最后,可以在任务的清单中配置 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")
}
为了获得更多的控制,也可以使用闭包。该闭包会接收一个 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 文件,请配置任务的清单文件以设置 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 文件使用与常规引导打包 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 放在类路径上,你可以以特殊模式启动你的应用程序,使得引导代码运行与你的应用程序完全不同的内容,例如提取层的操作。如果你希望排除这个依赖,你可以通过以下方式实现:
- 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
方法定义了各层应被写入的顺序。
在 application
和 dependencies
部分中,使用嵌套的 intoLayer
闭包来为某一层声明内容。这些闭包按照从上到下的定义顺序进行评估。任何未被前面的 intoLayer
闭包声明的内容,仍然可供后续的闭包使用。
intoLayer
闭包通过嵌套的 include
和 exclude
调用来声明内容。application
闭包使用 Ant 风格路径匹配来处理 include/exclude 参数。dependencies
部分使用 group:artifact[:version]
模式。它还提供了 includeProjectDependencies()
和 excludeProjectDependencies()
方法,可用于包含或排除项目依赖项。
如果没有进行 include
调用,那么所有内容(未被之前的闭包声明的)都将被纳入考虑。
如果没有调用 exclude
,则不应用任何排除规则。
查看上面示例中的 dependencies
闭包,我们可以看到第一个 intoLayer
将为 application
层声明所有项目依赖项。接下来的 intoLayer
将为 snapshot-dependencies
层声明所有 SNAPSHOT 依赖项。第三个也是最后一个 intoLayer
将为 dependencies
层声明剩余的任何内容(在这种情况下,任何不是项目依赖项或 SNAPSHOT 的依赖项)。
application
闭包有类似的规则。首先将 org/springframework/boot/loader/**
内容分配给 spring-boot-loader
层。然后将剩余的所有类和资源分配给 application
层。
intoLayer
闭包的添加顺序通常与层的编写顺序不同。因此,必须始终调用 layerOrder
方法,并且必须涵盖所有通过 intoLayer
调用引用的层。