大家都知道,我们常用的SpringBoot项目,通过启动java-jarxxx.jar命令,终于可以在线运行了。大家有没有想过一个问题,就是当我们执行java-jar命令的时候,底层到底做了什么来启动我们的SpringBoot应用呢?或者一个SpringBoot应用程序是如何运行的?今天阿芬就带大家来看看。认识jar在介绍java-jar的运行原理之前,我们先来看看jar包中包含的内容。我们正在准备一个SpringBoot项目。我们可以在https://start.spring.io/上快速创建一个SpringBoot项目,下载对应版本的zip包并注册。下载项目后,我们可以在pom依赖中看到如下依赖。这个插件是我们构建可执行jar的前提。所以如果要打包成jar,就必须在pom.jar中加入这个插件。从一开始在.spring.io上创建的项目将默认带这个插件。org.springframework.bootspring-boot-maven-plugin接下来我们执行mvn包。执行后,我们可以在项目的target目录下看到如下两个jar包。我们先解压这两个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项目不是不知道,只要是符合Java标准规范的jar,都可以通过这条命令启动。根据Java官方文档,当-jar参数存在时,jar文件资源必须包含Main-Class指定的启动类,同时根据规范,资源文件MANIFEST.MF必须放在/META-INF/目录。对比上面的解压文件,我们可以看到左边的资源文件MANIFEST.MF中有如图所示的一行。可以看到这里的Main-Class属性配置了org.springframework.boot.loader.JarLauncher,而且如果你细心一点,你会发现我们项目的启动类也在这个文件中,用Start-Class字段来表示。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(){返回false;}@OverrideprotectedbooleanisNestedArchive(Archive.Entryentry){返回NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);}@Override保护圣ringgetArchiveEntryPathPrefix(){返回“BOOT-INF/”;}publicstaticvoidmain(String[]args)throwsException{newJarLauncher().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项目是如何启动的,请转发这篇文章给他。