SpringBoot的jar包如何启动
一、简介
使用过SprongBoot
打过jar
包的都应该知道,目标文件一般都会生成两个文件,一个是以.jar
的包,一个是.jar.original
文件。那么使用SpringBoot
会打出两个包,而.jar.original
的作用是什么呢?还有就是java -jar
是如何将一个SpringBoot
项目启动,之间都进行了那些的操作?
其实.jar.original
是maven
在SpringBoot
重新打包之前的原始jar
包,内部只包含了项目的用户类,不包含其他的依赖jar
包,生成之后,SpringBoot
重新打包之后,最后生成.jar
包,内部包含了原始jar
包以及其他的引用依赖。以下提及的jar
包都是SpringBoot
二次加工打的包。
二、jar包的内部结构
SpringBoot
打出的jar
包,可以直接通过解压的方式查看内部的构造。一般情况下有三个目录。
BOOT-INF
:这个文件夹下有两个文件夹classes
用来存放用户类,也就是原始jar.original
里的类;还有一个是lib
,就是这个原始jar.original
引用的依赖。META-INF
:这里是通过java -jar
启动的入口信息,记录了入口类的位置等信息。org
:Springboot loader
的代码,通过它来启动。
这里主要介绍一下/BOOT-INF/MANIFEST.MF
文件
Manifest-Version: 1.0
Implementation-Title: springboot-server
Implementation-Version: 0.0.1-SNAPSHOT
Archiver-Version: Plexus Archiver
Built-By: Administrator
Implementation-Vendor-Id: cn.com.springboot
Spring-Boot-Version: 1.5.13.RELEASE
Implementation-Vendor: Pivotal Software, Inc.
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: cn.com.springboot.center.AuthEenterBootstrap
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Created-By: Apache Maven 3.6.1
Build-Jdk: 1.8.0_241
Implementation-URL: http://projects.spring.io/spring-boot/auth-server/
Main-Class
:记录了java -jar
的启动入口,当使用该命令启动时就会调用这个入口类的main
方法,显然可以看出,Springboot
转移了启动的入口,不是用户编写的xxx.xxx.BootStrap
的那个入口类。
Start-Class
:记录了用户编写的xxx.xxx.BootStrap
的那个入口类,当内嵌的jar
包加载完成之后,会使用LaunchedURLClassLoader
线程加载类来加载这个用户编写的入口类。
三、加载过程
1.使用到的一些类
3.1.1 Archive
归档文件接口,实现迭代器接口,它有两个子类,一个是JarFileArchive
对jar
包文件使用,提供了返回这个jar
文件对应的url
、或者这个jar
文件的MANIFEST
文件数据信息等操作。是ExplodedArchive
是文件目录的使用也有获取这个目录url
的方法,以及获取这个目录下的所有Archive
文件方法。
3.1.2 Launcher
启动程序的基类,这边最后是通过JarLauncher#main()
来启动。ExecutableArchiveLauncher
是抽象类,提供了获取Start-Class
类路径的方法,以及是否还有内嵌对应文件的判断方法和获取到内嵌对应文件集合的后置处理方法的抽象,由子类JarLauncher
和WarLauncher
自行实现。
3.1.3 Spring.loader下的JarFile和JarEntry
jarFile
继承于jar.util.jar.JarFile
,JarEntry
继承于java.util.jar.JarEntry
,对原始的一些方法进行重写覆盖。每一个JarFileArchive
都拥有一个JarFile
方法,用于存储这个jar
包对应的文件,而每一个JarFile
都有一个JarFileEntries
,JarFileEntries
是一个迭代器。总的来说,在解析jar
包时,会将jar
包内的文件封装成JarEntry
对象后由JarFile
对象保存文件列表的迭代器。所以JarFileArchive
和JarFileEntries
之间是通过JarFile
连接,二者都可以获取到JarFile
对象。
2.过程分析
从MANIFEST.MF
文件中的Main-class
指向入口开始。
创建JarLauncher
并且通过它的launch()
方法开始加载jar
包内部信息。
public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}
JarLauncher
的空构造方法时一个空实现,刚开始看的时候还懵了一下,以为是在后续的操作中去加载的文件,其实不然,在创建时由父类ExecutableArchiveLauncher
的构造方法去加载的文件。
public ExecutableArchiveLauncher() {
try {
// 加载为归档文件对象
this.archive = createArchive();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
/**
* 具体的加载方法
*/
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);
}
// 判断路径是否是一个文件夹,是则返回ExplodedArchive对象,否则返回JarFileArchive
return (root.isDirectory() ?