什么是可执行 jar
可执行 jar 是可以直接使用 Java -jar 命令启动的 jar 包
一般可执行 jar 的 MANIFEST.MF 文件中需要包含 classpath、main-class 等信息
如何打包可执行 jar
一种是使用 Java 自带的 jar 命令
更好的方式是使用专门的 Java 构建打包工具,如 Apache Maven
Maven 打包可执行 jar 的几种方式
maven-jar-plugin + maven-dependency-plugin
maven-assembly-plugin
maven-shade-plugin
spring-boot-maven-plugin
pom 配置
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>
${project.build.directory}/libs
</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>libs/</classpathPrefix>
<mainClass>com.example.Application</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
打包过程
maven-dependency-plugin 负责将所依赖的其他 jar 打包到指定目录 outputDirectory
maven-jar-plugin 将代码打包到一个 jar 中,并且配置 MANIFEST.MF 的 ClassPath 和主启动类,classpathPrefix 需要和 maven-dependency-plugin 的输出目录保持一致
输出内容如图
MANIFEST.MF 内容示例如下
Manifest-Version: 1.0
Built-By: Cade
Class-Path: libs/logback-classic-1.2.5.jar libs/logback-core-1.2.5.jar
libs/slf4j-api-1.7.31.jar libs/jackson-databind-2.12.4.jar libs/jack
son-annotations-2.12.4.jar libs/jackson-core-2.12.4.jar libs/lombok-1
.18.20.jar
Created-By: Apache Maven 3.8.1
Build-Jdk: 1.8.0_302
Main-Class: com.example.Application
pom 配置
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<archive>
<manifest>
<mainClass>
com.example.Application
</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</execution>
</executions>
</plugin>
打包过程
将依赖的第三方 jar 中的 class 文件配合主类一起打进 jar 包,不需要额外指定 ClassPath,因为所有的 class 文件及其包结构都被放在 jar 包的顶层,即 META-INF 的同级目录
pom 配置
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.example.Application</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
打包过程
maven-shade-plugin 和 maven-assembly-plugin 打包出的结果比较类似
maven-shade-plugin 支持通过字节码分析来自动修剪不必要的依赖,也支持添加前缀包名的方式来解决依赖冲突
pom 配置
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.6.6</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<classifier>spring-boot</classifier>
<mainClass>
com.example.Application
</mainClass>
</configuration>
</execution>
</executions>
</plugin>
打包过程
spring-boot-maven-plugin 是 Spring Boot 提供的 Maven 打包插件,会把项目 class 文件放到 BOOT-INF 下的 classes 目录,第三方 jar 包放在 BOOT-INF 下的 lib 目录
MANIFEST.MF 内容如下
Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Archiver-Version: Plexus Archiver
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.example.Application
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Version: 2.6.6
Created-By: Apache Maven 3.8.1
Build-Jdk: 1.8.0_302
Main-Class: org.springframework.boot.loader.JarLauncher
可以看到 Main-Class 并不是 com.example.Application,而是 Spring Boot Loader 提供的启动工具类
JarLauncher 可以看作是 Spring Boot Loader 提供的一套用于执行 Spring Boot 打包出来的 jar 的标准
JarLauncher 的 main 方法是程序的真正入口,启动时会先加载 BOOT-INF 下的 class 文件和依赖的 jar 包,然后再通过反射调用代码中启动类的 main 方法