本篇文章将为大家讲述关于 SpringBoot 项目工程完成后,是如何通过 java-jar 命令来启动的,以及介绍 java-jar 命令的详细内容。希望本篇文章能够帮助到大家的学习!

Pre

大家开发的基于Spring Boot 的应用 ,jar形式, 发布的时候,绝大部分都是使用java -jar 启动。 得益于Spring Boot 的封装 , 再也不用操心搭建tomcat等相关web容器le , 一切变得非常美好, 那SpringBoot是怎么做到的呢?

引导

新建工程 打包 启动

我们新创建一个Spring Boot的工程

其中打包的配置为

    <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>

先打包一下

查看target目录

然后启动

java -jar 干啥的

我们先看看 java -jar 干了啥 ?

在oracle官网找到了该命令的描述:

If the -jar option is specified, its argument is the name of the JAR file containing class and resource files for the application. The startup class must be indicated by the Main-Class manifest header in its source code.

使用-jar参数时,后面的参数是的jar 【spring-0.0.1-SNAPSHOT.jar】,该jar文件中包含的是class和资源文件; 在manifest文件中有Main-Class的定义;Main-Class的源码中指定了整个应用的启动类;

简单来说: java -jar会去找jar中的manifest文件,去找到Main-Class对应的真正的启动类;

那看看去吧

咦 ,这个Main-Class 是Spring Boot 的。

我们还看到有个Start Class

官方文档中,只提到过Main-Class ,并没有提到Start-Class;
Start-Class的值是com.artisan.spring.Application,这是我们的java代码中的唯一类,包含main方法, 是能够真正的应用启动类

所以问题就来了:理论上看,执行java -jar命令时JarLauncher类会被执行,但实际上是com.artisan.spring.Application被执行了,这其中发生了什么呢?why?

打包插件

事实上,Java没有提供任何标准的方式来加载嵌套的jar文件 (jar中包含jar ,即Spring Boot 中的fat jar)

Spring Boot 默认的打包插件如下:

 <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>

执行maven clean package之后,会生成两个文件,刚才我们也看到了

spring-boot-maven-plugin简介

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*

spring-boot-maven-plugin的repackage在代码层面调用了RepackageMojoexecute方法,而在该方法中又调用了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 = filterDependencies(this.project.getArtifacts(),getFilters(getAdditionalFilters()));// 将artifacts转换成librariesLibraries libraries = new ArtifactsLibraries(artifacts, this.requiresUnpack,getLog());try {// 获得Spring Boot启动脚本LaunchScript launchScript = getLaunchScript();// 执行重新打包,生成fat jarrepackager.repackage(target, libraries, launchScript);}catch (IOException ex) {throw new MojoExecutionException(ex.getMessage(), ex);}// 将maven生成的jar更新成.original文件updateArtifact(source, target, repackager.getBackupFile());
}

执行以上命令之后,便生成了打包结果对应的两个文件。

包结构

下面针对文件的内容和结构进行一探究竟。

spring-0.0.1-SNAPSHOT.jar
├── META-INF
│   └── maven(主要是pom文件)
│   └── MANIFEST.MF
├── BOOT-INF
│   ├── classes
│   │   └── 应用程序类
│   └── lib
│       └── 第三方依赖jar
└── org└── springframework└── boot└── loader└── springboot启动程序

META-INF内容

Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: spring
Implementation-Version: 0.0.1-SNAPSHOT
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.artisan.spring.Application
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.4.1
Created-By: Maven Jar Plugin 3.2.0
Main-Class: org.springframework.boot.loader.JarLauncher
  • Main-Class:org.springframework.boot.loader.JarLauncher ,这个是jar启动的Main函数Start-Class: com.artisan.spring.Application,这个是我们应用自己的Main函数

Archive的概念

在继续了解底层概念和原理之前,我们先来了解一下Archive的概念:

  • archive即归档文件,这个概念在linux下比较常见
  • 通常就是一个tar/zip格式的压缩包
  • jar是zip格式

SpringBoot抽象了Archive的概念,一个Archive可以是jar(JarFileArchive),可以是一个文件目录(ExplodedArchive),可以抽象为统一访问资源的逻辑层

关于Spring Boot中Archive的源码如下:

public interface Archive extends Iterable<Archive.Entry> {// 获取该归档的urlURL getUrl() throws MalformedURLException;// 获取jar!/META-INF/MANIFEST.MF或[ArchiveDir]/META-INF/MANIFEST.MFManifest getManifest() throws IOException;// 获取jar!/BOOT-INF/lib/*.jar或[ArchiveDir]/BOOT-INF/lib/*.jarList<Archive> getNestedArchives(EntryFilter filter) throws IOException;
}

SpringBoot定义了一个接口用于描述资源,也就是org.springframework.boot.loader.archive.Archive

该接口有两个实现,分别是

  • org.springframework.boot.loader.archive.ExplodedArchive
  • org.springframework.boot.loader.archive.JarFileArchive

前者用于在文件夹目录下寻找资源,后者用于在jar包环境下寻找资源。而在SpringBoot打包的fatJar中,则是使用后者JarFileArchive

JarFile

JarFile:对jar包的封装,每个JarFileArchive都会对应一个JarFile。

JarFile被构造的时候会解析内部结构,去获取jar包里的各个文件或文件夹,这些文件或文件夹会被封装到Entry中,也存储在JarFileArchive中。如果Entry是个jar,会解析成JarFileArchive。

比如一个JarFileArchive对应的URL为:

jar:file:/Users/format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/

它对应的JarFile为:

/Users/format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar

这个JarFile有很多Entry,比如:

META-INF/
META-INF/MANIFEST.MF
spring/
spring/study/
....
spring/study/executablejar/ExecutableJarApplication.class
lib/spring-boot-starter-1.3.5.RELEASE.jar
lib/spring-boot-1.3.5.RELEASE.jar
...

JarFileArchive内部的一些依赖jar对应的URL(SpringBoot使用org.springframework.boot.loader.jar.Handler处理器来处理这些URL):

jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-starter-web-1.3.5.RELEASE.jar!/
jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-loader-1.3.5.RELEASE.jar!/org/springframework/boot/loader/JarLauncher.class

我们看到如果有jar包中包含jar,或者jar包中包含jar包里面的class文件,那么会使用 !/ 分隔开,这种方式只有org.springframework.boot.loader.jar.Handler能处理,它是SpringBoot内部扩展出来的一种URL协议。

JarLauncher工作流程

从MANIFEST.MF可以看到Main函数是JarLauncher,下面来分析它的工作流程。JarLauncher类的继承结构是:

class JarLauncher extends ExecutableArchiveLauncher
class ExecutableArchiveLauncher extends Launcher

Launcher for JAR based archives. This launcher assumes that dependency jars are included inside a /BOOT-INF/lib directory and that application classes are included inside a /BOOT-INF/classes directory.

什么意思呢?

按照定义,JarLauncher可以加载内部/BOOT-INF/lib下的jar及/BOOT-INF/classes下的应用class。

public class JarLauncher extends ExecutableArchiveLauncher {public JarLauncher() {}public static void main(String[] args) throws Exception {new JarLauncher().launch(args);}
}

其主入口新建了JarLauncher并调用父类Launcher中的launch方法启动程序。在创建JarLauncher时,父类ExecutableArchiveLauncher找到自己所在的jar,并创建archive。

JarLauncher继承于org.springframework.boot.loader.ExecutableArchiveLauncher。该类的无参构造方法最主要的功能就是构建了当前main方法所在的FatJar的JarFileArchive对象。

下面来看launch方法。该方法主要是做了2个事情:

(1)以FatJar为file作为入参,构造JarFileArchive对象。获取其中所有的资源目标,取得其Url,将这些URL作为参数,构建了一个URLClassLoader

(2)以第一步构建的ClassLoader加载MANIFEST.MF文件中Start-Class指向的业务类,并且执行静态方法main。进而启动整个程序。

public abstract class ExecutableArchiveLauncher extends Launcher {private final Archive archive;public ExecutableArchiveLauncher() {try {// 找到自己所在的jar,并创建Archivethis.archive = createArchive();}catch (Exception ex) {throw new IllegalStateException(ex);}}
}public abstract class Launcher {protected final Archive createArchive() throws Exception {ProtectionDomain protectionDomain = getClass().getProtectionDomain();CodeSource codeSource = protectionDomain.getCodeSource();URI location = (codeSource == null ? null : codeSource.getLocation().toURI());String path = (location == null ? null : location.getSchemeSpecificPart());if (path == null) {throw new IllegalStateException("Unable to determine code source archive");}File root = new File(path);if (!root.exists()) {throw new IllegalStateException("Unable to determine code source archive from " + root);}return (root.isDirectory() ? new ExplodedArchive(root): new JarFileArchive(root));}
}

在Launcher的launch方法中,通过以上archive的getNestedArchives方法找到/BOOT-INF/lib下所有jar及/BOOT-INF/classes目录所对应的archive,通过这些archives的url生成LaunchedURLClassLoader,并将其设置为线程上下文类加载器,启动应用。

至此,才执行我们应用程序主入口类的main方法,所有应用程序类文件均可通过/BOOT-INF/classes加载,所有依赖的第三方jar均可通过/BOOT-INF/lib加载。

小结

  • JarLauncher通过加载BOOT-INF/classes目录及BOOT-INF/lib目录下jar文件,实现了fat jar的启动。
  • SpringBoot通过扩展JarFile、JarURLConnection及URLStreamHandler,实现了jar in jar中资源的加载。
  • SpringBoot通过扩展URLClassLoader–LauncherURLClassLoader,实现了jar in jar中class文件的加载。
  • WarLauncher通过加载WEB-INF/classes目录及WEB-INF/lib和WEB-INF/lib-provided目录下的jar文件,实现了war文件的直接启动及web容器中的启动。

通过spring-boot-plugin 生成了MANIFEST.MF , main-class 指定运行java -jar的主程序把依赖的jar文件 打包在fat jar.

以上就是关于 java -jar命令的详细内容以及 SpringBoot 如何使用 java -jar命令来启动项目的具体过程。

详解java -jar命令及SpringBoot通过java -jav启动的过程相关推荐

  1. java -jar 工作原理_Spring Boot 的java -jar命令启动原理详解

    导语 在运用Spring Boot 后,我们基本上摆脱之前项目每次上线的时候把项目打成war包.当然也不排除一些奇葩的规定,必须要用war包上线,不过很多时候,我们对一些东西只是处在使用的阶段,并不会 ...

  2. java -jar命令详解

    java -jar命令用于执行jar程序,常用的写法有5种: 第1种 java -jar demo.jar 说明:用这种方法启动后,不能继续执行其它命令了,如果想要继续执行其它命令,需要退出当前命令运 ...

  3. java -jar命令引导启动Springboot项目的那点事

    前言:Java官方规定java -jar命令引导的具体启动类必须配置在MANIFEST.MF资源的Main-Class属性中.比如通过java -jar XXX.jar来运行应用时,如不做特殊设置就要 ...

  4. nohup命令解决SpringBoot/java -jar命令启动项目运行一段时间自动停止问题

    nohup命令解决SpringBoot/java -jar命令启动项目运行一段时间自动停止问题 问题背景 有一个springboot项目,放在测试服务器跑,但是隔一段时间,就会GG,要搞清楚怎么回事. ...

  5. Elastic search入门到集群实战操作详解(原生API操作、springboot整合操作)-step1

    Elastic search入门到集群实战操作详解(原生API操作.springboot整合操作)-step2 https://blog.csdn.net/qq_45441466/article/de ...

  6. Android编译详解之lunch命令 【转】

    本文转载自: Android编译详解之lunch命令 (2012-10-08 10:27:55) 转载 ▼ 标签: it 分类: android内核剖析 Android的优势就在于其开源,手机和平板生 ...

  7. linux中jstack命令详解,Linux jstack命令详解

    jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息. 如果是在64位机器上,需要指定选项"-J-d64",Windows的jstack ...

  8. java -jar命令参数的单横杠-和双横杠--用法

    java -jar命令参数的单横杠-和双横杠–用法 如下是一条典型的jar包启动参数,相信很多人都有用过: java -jar -Xms4096M xxx.jar --server.port=8088 ...

  9. 【视频】详解Scala中的类及与Java的详细区别

    详解Scala中的类及与Java的详细区别

最新文章

  1. Python中的元类是什么?
  2. java迷宫队列实现_Creator 迷宫生成: DFS 与 BFS 算法实现
  3. java语言如何跳转界面_在java中spring mvc页面如何跳转,详细图解
  4. Windows server 2016简单部署DNS服务,正向查找和委派
  5. Spring Boot项目中使用RestTemplate时出现乱码时的解决方案
  6. 能在市场上大概率赚钱的人类型
  7. sqoop简单的demo 先跑起来再说
  8. vue 动态scss变量,包含16进制转rgba,rgba转16进制
  9. python图片批量转换成灰度图像
  10. [707]Apache NiFi安装及简单使用
  11. 【DFT】可测性设计(一)扫描测试
  12. vscode绿色、护眼色,vue自动格式化配置参考
  13. 笔记本电脑重装win7/win10系统教程
  14. u深度重装系统详细教程_u深度u盘启动盘制作工具教程_u深度u盘装系统教程
  15. imperva 默认策略添加例外
  16. SQL行转列,列转行
  17. uniapp连接到微信小程序调试全过程以及遇到的bug
  18. 尚硅谷MySQL基础部分的笔记
  19. Linux rpm命令用法
  20. U盘里的东西删除了怎么恢复?u盘删除的内容怎么找回

热门文章

  1. 基2FFT的matlab实现
  2. jQuery动态绑定事件或者原生js动态绑定事件
  3. 微博营销中的 KOL 分析
  4. Django:在DDTCMS中使用Photologue做相册并增加封面的探索
  5. ballerina 学习 三十二 编写安全的程序
  6. AI行为识别:安防主动预警
  7. 网络安全开源扫描引擎
  8. C语言输出矩阵的主对角线和以及次对角线和
  9. SQL Server数据并发处理
  10. 2022-2028年中国财税信息化行业市场行情动态及发展趋向分析报告