大家都知道,我们常用的SpringBoot项目最后上线运行时,都是通过启动java-jarxxx.jar命令来运行的。大家有没有想过一个问题,就是当我们执行java-jar命令的时候,底层到底做了什么来启动我们的SpringBoot应用呢?或者SpringBoot应用程序如何工作?今天阿芬就带大家来看看。认识jar在介绍java-jar的运行原理之前,我们先来看看jar包中包含的内容。我们正在准备一个SpringBoot项目。我们可以在https://start.spring.io/上快速创建一个SpringBoot项目,下载一个版本对应的zip包并注册。下载项目后,我们可以在pomdependencies中看到如下依赖。这个插件是我们构建可执行jar的前提,所以如果要打包成jar,就必须在pom.jar中加入这个插件。Start.spring.io在上面创建的项目默认会带上这个插件。org.springframework.bootspring-boot-maven-plugin接下来我们执行mvn包。执行之后,我们可以在项目的target目录下看到如下两个jar包。让我们解压这两个罐子看看内容。.original后缀的jar需要把.original去掉即可解压。jar文件的解压和我们平时的zip解压是一样的。jar文件是以zip压缩格式存储的,所以任何可以解压zip文件的软件都可以解压jar文件。解压后,我们对比两个解压后的文件,可以发现两个文件夹的内容差别很大,如下图,左边是demo-jar-0.0.1-SNAPSHOT.jar,右边是相应的原始罐子。还有一些相同的文件夹和文件,比如META-INF、application.properties等,我们可以清楚的看到,项目需要依赖的所有库文件都存放在左侧压缩包中的lib文件夹中.所以我们可以大胆猜测,左边的压缩包是spring-boot-maven-plugin插件生成的,帮助我们调整依赖库和对应文件的目录结构,事实确实如此。java-jar的原理首先我们要知道这个java-jar并不是什么新东西,而是java本身自带的一个命令,而java-jar命令执行的时候,这个命令本身是否是一个SpringBoot项目对这个jar有没有察觉,只要jar符合Java标准规范,就可以通过这个命令启动。根据Java官方文档,当存在-jar参数时,jar文件资源必须包含Main-Class指定的启动类,同时根据规范,资源文件MANIFEST.MF必须放在/META-INF/目录下。对比上面的解压文件,我们可以看到左边的资源文件MANIFEST.MF中有如图所示的一行。可以看到这里的Main-Class属性配置了org.springframework.boot.loader.JarLauncher,细心一点的你会发现我们项目的启动类也在这个文件中,通过Start-类字段表示属性Start-Class不是官方的Java属性。那么我们来大胆猜测一下。当我们执行java-jar的时候,因为我们的jar里面有一个MANIFEST.MF文件,里面有Main-Class属性,配置了org.springframework.boot.loader.JarLauncher类,通过调用JarLauncher类结合Start-class属性来引导我们项目的启动类启动。接下来,我们将通过源码来验证这个猜想。因为JarLauncher类在spring-boot-loader模块中,所以我们在pom文件中添加如下依赖,然后我们就可以下载源码进行跟踪了。org.springframework.bootspring-boot-loaderprovided通过源代码我们可以看到JarLauncher类的代码如下包org.springframework.boot.loader;导入org.springframework.boot.loader.archive.Archive;导入org.springframework.boot.loader.archive.Archive.EntryFilter;公共类JarLauncher扩展ExecutableArchiveLauncher{staticfinalEntryFilterNESTED_ARCHIVE_ENTRY_FILTER=(entry)->{if(entry.isDirectory()){returnentry.getName().equals("BOOT-INF/classes/");}returnentry.getName().startsWith("BOOT-INF/lib/");};publicJarLauncher(){}protectedJarLauncher(Archivearchive){super(archive);}@OverrideprotectedbooleanisPostProcessingClassPathArchives(){returnfalse;}@Override保护dbooleanisNestedArchive(Archive.Entryentry){returnNESTED_ARCHIVE_ENTRY_FILTER.matches(entry);}}@OverrideprotectedStringgetArchiveEntryPathPrefix(){return"BOOT-INF/";}publicstaticvoidmain(String[]args)throwsnewException{JarLauncher().launch(args);}}有两点我们可以注意。第一个是这个类有一个main方法,这就是java-jar命令可以启动的原因。毕竟java程序都是通过main方法运行的第二件事就是有两条路径BOOT-INF/classes/和BOOT-INF/lib/。这两条路径正好是我们的源码路径和第三方依赖路径。JarLauncher类中的main()方法主要是运行Launcher中的launch()方法。这些类的关系图如下所示。顺着代码我们可以看到最终调用了run()方法,这里的参数mainClass和launchClass是通过下面的逻辑获取的,都是通过资源文件中的Start-Class获取的。这是我们项目的启动类,可见我们上面的猜测是正确的。展开上面的类图,我们还可以看到除了JarLauncher之外还有一个WarLauncher类。确实,我们的SpringBoot项目也可以配置为war进行部署。我们只需要将打包插件中的jar换成war即可。大家可以尝试自己重新打包解压分析。此处war包部署方式仅供研究学习。SpringBoot应用尽量使用Jar来部署。总结通过以上内容我们知道,当我们执行java-jar时,根据java官方规范,会引导jar包中MANIFEST.MF文件中Main-Class属性对应的启动类,启动类必须包含main()方法。对于我们的SpringBoot项目构建的jar包,除了Main-Class属性之外,还会有一个Start-Class属性绑定到我们项目的启动类上。当我们执行java-jar时,优先级是org.springframework.boot.loader.JarLauncher#main方法,它会通过启动Start-Class属性在内部启动我们的应用程序代码。通过上面的分析,大家对SpringBoot是如何通过java-jar启动的有了详细的了解。下次有人问你SpringBoot项目是怎么启动的,请转发这篇文章给他。如果觉得我们的文章对您有帮助,欢迎点赞、分享、评论转发,一键三连。