本章我们将环绕 jar 包和 war 包的运作事理及干系操作进行讲解。
SpringBoo的jar 包Spring Boot 的 jar 包项目发布形式大略、快捷且内置 web 容器,因此 Spring Boot 将其作为默认选项。在享受便利的同时,我们也须要多少理解一下 Spring Boot 的 jar 包是如何天生的,以及如何通过 jar 包启动运行。本节从 jar 包的天生、构造、运作事理来剖析 Spring Boot的实现。
Spring Boot 的可实行 jar 包又称作 fat jar”,是包含所有三方依赖的 jar。它与传统 jar 包最大的不同是包含了一个 lib 目录和内嵌了 web 容器(以下均以 tomcat 为例)。

jar 包通 常是由集成在 pom.xml 文件中的 maven 插件来天生的。
配置在 pom 文件 build 元素中的 plugins 内。
<build><plugins><plugin><groupId>org . springframework. boot</groupId><artifactId>spring- boot-maven- plugin</ artifactId></plugin></plugins></build>
spring-boot-maven-plugin 项目存在于 spring-boot-tools 目录中。spring-boot-maven-plugin默认有 5 个 goals: repackage、 run、 start、 stop、 build-info。在打包的时候默认利用的是 repackage。
spring-boot-maven-plugin 的 repackage 能够将 mvn package 天生的软件包,再次打包为可实行的软件包,并将 mvn package 天生的软件包重命名为.original。
这就为什么当实行 maven clean package 时,spring-boot-maven-plugin 会在 target 目录下天生两个 jar 文件。
spring - learn-0.0.1- SNAPSHOT. jar
spring - learn-0.0.1- SNAPSHOT . jar . original个中我们可以将 spring-learn-0.0. 1-SNAPSHOTjar.original 文件的后缀 original 去掉,天生的新jar包便是包含业务代码的包(普通的jar包) 。其余的spring-learn-0.0. 1-SNAPSHOTjar包则是在 Spring Boot 中通过 jar jar 启动的包,它包含了运用的依赖,以及 spring boot 干系 class。
spring-boot-maven-plugin 的 repackage 在代码层面调用了 RepackageMojo 的 execute 方法。RepackageMojo 类便是 供应重新打包现有的 jar 或 war 包文件,使得它们可以利用 javajar 来进行启动。
RepackageMojo 的 execute 方法如下。
@Overridepublic void execute() throws MojoExecut ionException, MojoFailureExceptionif (this. project . getPackaging() . equals("pom")) {getLog() . debug("repackage goal could not be applied to pom project.");return;if (this.skip) {getLog() . debug("skipping repackaging as per configuration.");return;repackage();}
在 execute 方法中止定了是否为 pom 项目和是否跳过,如果是,则打印 debug 日志并返回;否则连续实行 repackage 方法。RepackageMojo 中的 repackage 方法干系源代码及操作解析如下。
private void repackage() throws MojoExecutionException {// maven 天生的 jar, 终极的命名将加上. original 后缀Artifact source = getSourceArtifact();//终极为可实行 jar,即 fat jarFile target = getTargetFile();//获取重新打包器,将 maven 生 成的 jar 重新打包成可实行 jarRepackager repackager = getRepackager(source . getFile());//查找并过滤项目运行时依赖的 jarSet<Artifact> artifacts = filterDependenc ies(this . project. getArtifacts(),getFilters(getAdditionalFilters()));//将 artifacts 转换成 L ibrariesLibraries libraries = new ArtifactsLibraries(artifacts, this . requiresUnpak,getLog());ry {/得到 Spring Boot 启动脚本LaunchScript launchScript = getLaunchScript();//实行重新打包,天生 fat jarrepackager . repackage(target, libraries, launchScript);catch (IOException ex) {throw new MojoExecut ionException(ex. getMessage(), ex); }将 maven 天生的 jar 更新成 original 文件updateArtifact(source, target, repackager . getBackupFile());}
关于全体 repackage 方法的操作流程在上面代码中已经进行相应注释解释,其基本过程为:得到 maven 天生的普通 jar 包、得到目标 File 工具、得到重新打包器、得到依赖 jar 包、 得到启动脚本,末了通过重新打包器进行重新打包为可通过 java -jar 实行的 jar 包。
个中我们重点看获取 Repackager 的方法 getRepackager 的源代码。
private Repackager getRepackager(File source) {Repackager repackager = new Repackager(source, this . layoutFactory);repackager . addMainClassTimeoutWarningL istener(new LoggingMainClassTimeoutWarningl istener());//设置 main class 的名称,如果不指定, 则会查找第一个包含 main 方法的类// repackage 末了将会设置 org. springframework . boot . Loader. JarLauncherrepackager . setMainClass(this . mainClass);if (this.layout != null) {getLog(). info("Layout: "+this. layout);//比如,layout 返@org. springframework. boot. loader. tools. Layouts . Jarrepackager . setLayout(this . layout. layout());}return repackager;}
getRepackager 方法紧张是根据将要被转换的文件(jar 或 war) 创建了 Repackager 工具,并设置启动用的 MainClass 为 org. springframework.boot.loader.JarLauncher,该配置对应于 jar 包中 Manifest.MF 文件内的 MainClass 值。
同时,如果 layout 不为 null, 通过内部列举类 L ayoutType 供应的 layout 方法获取对应的重新打包的实现类,比如针对 jar 包的 org.springframework. boot.loader.tools.Layouts.Jar 类。
列举类 LayoutType 的定义如下。
public enum LayoutType {JAR(new Jar()),WAR(new War()),ZIP(new Expanded()),DIR(new Expanded()),NONE(new None());}
从 LayoutType 的定义可以看出,Spring Boot 实在是支持多种类型的 archive ( 即归档文件) : jar 类型、war 类型、zip 类型、 文件目录类型和 NONE。很显然,利用了相同的实现类来处理 ZIP 文件和 DIR 文件。
jar 类型为 Layouts 类的内部类,可以大略看一下 jar 类型的处理类都包含 了哪些内容。
public static class Jar implements RepackagingLayout {//获取详细的 L ancher 类全路径@Overridepublic String getLauncherClassName() {return "org. springframework. boot . loader.Jarlauncher";/得到详细的依赖 jar 包路径@Overridepublic String getL ibraryDestination(String libraryName, LibraryScope scopreturn "BOOT - INF/lib/";//获取重新打包的 class 文件路径@Overridepublic String getRepackagedClassesLocation() {return "BOOT -INF/classes/";}}
通过源代码可以看出,jar 类型的归档文件(jar 包) 中包含了 jar 包启动的 Main-class ( JarLauncher )BOOT-INF/lib/目录和 BOOT-INF/classes/目录。如果看 Expanded 和 None 类,会创造它们又继续自 jar。
末了,我们大略看一下 RepackageMojo 中的 repackage 调用所获取的 Repackager 的repackage 方法。Repackager 中 repackage 方法源码如下。
public void repackage(File destination, Libraries libraries, LaunchScript 1aunchScript) throws IOException {//校验目标文件F (destination == null| | destination. isDirectory())throw new illegalArgumentException("Invalid destination");//校验依赖库f (libraries = null) {throw new IllegalArgumentException("Libraries must not be nu1l");//校验是否存在对应的 Layout, 如果不存在则创建if (this.layout == null) {this. layout = getL ayoutFactory() . getLayout(this . source);destination = destination. getAbsoluteFile();File workingSource = this. source;//检讨是否已经重新打包if (alreadyRepackaged() && this. source.equals(destination)) {//如果目标文件和 source 相同, 则删除原有备份文件( . original 结尾的), 重新备份 source 文件if (this. source . equals(destination)) {workingSource = getBackupFile();workingSource. delete();renameFile(this. source, workingSource);destination. delete();try {try (JarFile jarFileSource = new JarFile(workingSource)) {//核心功能便是创建 JarWriter 向文件指定文件中写入内容repackage(jarFileSource, destination, libraries, launchScript); }finally {if (!this . backupSource && !this. source . equals (workingSource)) {deleteFile(workingSource);}}}
上述代码的核心业务逻辑如下。
.校验各种参数(文件和路径是否存在)。
.备份待重新打包的文件以.original 结尾, 如果已经存在备份文件则先实行删除操作。
:天生目标文件之前,先打消一下目标文件。
调用重载的 repackage 方法,进行详细(jar 包)文件的天生和 MANIFESTMF 的信息写入。
.末了,判断并实行 workingSource 的打消操作。
用一句话总结上述过程:当符合条件时,对原有 jar 包文件进行备份,并天生新的可以通过 jar-jar 启动的文件。
关于重新打包的 jar 的目录构造及 MANIFEST.MF 文件中的信息,我们将不才一节进行讲解。
jar包的构造在上一节中,通过 spring-boot-maven-plugin 生 成了可实行的 jar 包,下面剖析-下 jar 包spring-learn-0.0.1-SNAPSHOT.jar的目录构造。
在上述构造中,BOOT-INF/classes 目录中存放业务代码,BOOT-INF/ib 目录中存放了除java 虚拟机之外的所有依赖; org 目 录中存放了 Spring Boot 用来启动 jar 包的干系 class文件; META-INF 目录中存放了 MANIFEST.MF、maven 信息和 spring factories 文件。
个中,Manifest.MF 文件常日被用来定义扩展或档案打包干系数据,它是一个元数据文件,数据格式为名/值对。一个可实行的 jar 文件须要通过该文件来指出该程序的主类。
Manifest-Version: 1.0Implementation-Title: spring-learnImplementation-Version: 0. 0.1-SNAPSHOTStart-Class: com. secbro2. learn. SpringLearnApplicationSpring - Boot-Classes: B0OT-INF/classes/Spring-Boot-Lib: B0OT-INF/lib/Build-Jdk-Spec: 1.8Spring- Boot -Version: 2.2.1. RELEASECreated-By: Maven Archiver 3.4.0Main-Class: org. springframework . boot . loader .Jarlauncher
Manifest.MF 文件中定义 Main-Class 设置为org. springframework.boot.loader.JarLauncher, 也 就 是 说 , jar 包 程 序 启 动 入 口 为JarL .auncher 类的 main 方法。JarLauncher 类位 于 spring-boot-loader 项目中,在 jar 包的 org 目录中便存储着 Launcher 干系类的 class 文件。
项目的弓导类定义在 Start-Class 属性中,须要把稳的是,Start-Class 属性并非 Java 标准的 Manifest.MF 属性。
本文给大家讲解的内容是SpringBoot打包支配解析:jar包的天生和构造下篇文章给大家讲解的是SpringBoot打包支配解析:Launcher实现事理;以为文章不错的朋友可以转发此文关注小编;感谢大家的支持!