SpringBoot的jar包如何启动的实现
⽬录
⼀、简介
⼆、jar包的内部结构
三、加载过程
1.使⽤到的⼀些类
2.过程分析
四、总结
⼀、简介
使⽤过SprongBoot打过jar包的都应该知道,⽬标⽂件⼀般都会⽣成两个⽂件,⼀个是以.jar的包,⼀个是.iginal⽂件。那么使⽤SpringBoot会打出两个包,⽽.iginal的作⽤是什么呢?还有就是java -jar是如何将⼀个SpringBoot项⽬启动,之间都进⾏了那些的操作?
其实.iginal是maven在SpringBoot重新打包之前的原始jar包,内部只包含了项⽬的⽤户类,不包含其他的依赖jar包,⽣成之后,SpringBoot重新打包之后,最后⽣成.jar包,内部包含了原始jar包以及其他的引⽤依赖。以下提及的jar包都是SpringBoot⼆次加⼯打的包。
⼆、jar包的内部结构
SpringBoot打出的jar包,可以直接通过解压的⽅式查看内部的构造。⼀般情况下有三个⽬录。
BOOT-INF:这个⽂件夹下有两个⽂件夹classes⽤来存放⽤户类,也就是原始iginal⾥的类;还有⼀个是lib,就是这个原始iginal引⽤的依赖。
META-INF:这⾥是通过java -jar启动的⼊⼝信息,记录了⼊⼝类的位置等信息。
org:Springboot loader的代码,通过它来启动。
这⾥主要介绍⼀下/BOOT-INF/MANIFEST.MF⽂件
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: AuthEenterBootstrap
Main-Class:记录了java -jar的启动⼊⼝,当使⽤该命令启动时就会调⽤这个⼊⼝类的main⽅法,显然可以看出,Springboot转移了启动的⼊⼝,不是⽤户编写的BootStrap的那个⼊⼝类。
Start-Class:记录了⽤户编写的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);
maven打包本地jar包}
JarLauncher的空构造⽅法时⼀个空实现,刚开始看的时候还懵了⼀下,以为是在后续的操作中去加载的⽂件,其实不然,在创建时由⽗类ExecutableArchiveLauncher的构造⽅法去加载的⽂件。
加载为归档⽂件对象:
this.archive = createArchive();
具体的加载⽅法:判断路径是否是⼀个⽂件夹,是则返回ExplodedArchive对象,否则返回JarFileArchive进⼊JarFileArchive类:通过这个new⽅法创建JarFile对象
public class JarFileArchive implements Archive {
public JarFileArchive(File file, URL url) throws IOException {
this(new JarFile(file));
this.url = url;
}
}
进⼊到JarFile⽅法:通过RandomAccessDataFile读取⽂件的内容,并传递给本类中的⽅法进⾏具体的解析。
public class JarFile extends java.util.jar.JarFile {
public JarFile(File file) throws IOException {
this(new RandomAccessDataFile(file));
}
}
进⼊jarLauncher的launch⽅法:注册URL协议的处理器,没有指定时,默认指向org.springframework.boot.loader包路径,获取类路径下的归档⽂件Archive并通过这些归档⽂件的URL,创建线程上下⽂类加载器,使⽤类加载器和⽤户编写的启动⼊⼝类,通过反射调⽤它的main⽅法。
protected void launch(String[] args) throws Exception {
ClassLoader classLoader = createClassLoader(getClassPathArchives());
launch(args, getMainClass(), classLoader);
}
JarLauncher的getClassPathArchives是在ExecutableArchiveLauncher中实现:获取归档⽂件中满⾜EntryFilterg过滤器的项,isNestedArchive⽅法由具体的之类实现。获取到当前归档⽂件下的所有⼦归档⽂件之后的后置操作,是⼀个扩展点。在JarLauncher中是⼀个空实现。
JarLauncher的具体实现,这⾥通过判断是否在BOOT-INF/lib/包下返回true也就是说只会把jar包下的BOOT-INF/lib/下的⽂件加载
为Archive对象
protected boolean isNestedArchive(Archive.Entry entry) {
if (entry.isDirectory()) {
Name().equals(BOOT_INF_CLASSES);
}
Name().startsWith(BOOT_INF_LIB);
}
JarFileArchive的getNestedArchives⽅法:若匹配器匹配到则获取内嵌归档⽂件。
具体的获取内嵌归档⽂件逻辑:根据具体的Entry对象,创建JarFile对象并封装成归档⽂件对象后返回。
protected Archive getNestedArchive(Entry entry) throws IOException {
try {
JarFile jarFile = NestedJarFile(jarEntry);
return new JarFileArchive(jarFile);
}
}
获取到参数entry对应的RandomAccessData对象,这⾥根据springboot扩展的url协议,在⽗路径的基础上添加!/来标记⼦包。
private JarFile createJarFileFromFileEntry(JarEntry entry) throws IOException {
RandomAccessData entryData = Name());
return new File, this.pathFromRoot + "!/" + Name(),
entryData, JarFileType.NESTED_JAR);
}
到这基本上读取jar内部信息,加载为对应归档⽂件对象的⼤概过程已经讲完了,接下来分析⼀下在获取到了整个jar的归档⽂件对象后的处理。
通过归档⽂件对象列表,获取对应的url信息,并通过url信息创建LaunchedURLClassLoader
protected ClassLoader createClassLoader(List<Archive> archives) {
List<URL> urls = new ArrayList<URL>(archives.size());
for (Archive archive : archives) {
urls.Url());
}
return Array(new URL[urls.size()]));
}
获取到对应的LaunchedUrlClassLoader类加载器之后,设置线程的上下⽂类加载器为该加载器。根据MANIFI.MF⽂件中的start-classs信息创建项⽬启动⼊⼝主类对象,并通过返回对象的run⽅法启动
protected void launch(String[] args, String mainClass, ClassLoader classLoader) {
Thread.currentThread().setContextClassLoader(classLoader);
createMainMethodRunner(mainClass, args, classLoader).run();
}
进⼊MainMethodRunner的run⽅法:先通过当前线程获取到main⼊⼝类,然后通过反射调⽤启动项⽬启动类的main⽅法
public void run() throws Exception {
Class<?> mainClass = Thread.currentThread().getContextClassLoader()
.loadClass(this.mainClassName);
Method mainMethod = DeclaredMethod("main", String[].class);
mainMethod.invoke(null, new Object[] { this.args });
}
最后来说⼀下这个LaunchedURLClassLoader,它继承于URLClassLoader,并重写了loadClass⽅法
LaunchedClassLoader的loadClass⽅法:调⽤⽗类loadClass⽅法,⾛正常委派流程,最终会被LaunchURLClassLoader加载。
@Override
protected Class<?> loadClass(String name, boolean resolve){
try {
try {
definePackageIfNecessary(name);
}
return super.loadClass(name, resolve);
}
}
进⼊URLClassLoader中根据springboot解析进⾏解析。根据名称将路径转化为以.class结尾的/分隔的格式。通过UrlClassPath对象根据路径获取资源类⽂件
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
String path = place('.', '/').concat(".class");
Resource res = Resource(path, false);
if (res != null) {
try {
return defineClass(name, res);
}
}
}
}
四、总结
Springboot主要实现了对URL加载⽅式进⾏了扩展,并且对⼀些对象Archive、JarFile、Entry等进⾏了抽象和扩展,最后使
⽤LaunchedUrlClassLoader来进⾏处理。
到此这篇关于SpringBoot的jar包如何启动的实现的⽂章就介绍到这了,更多相关SpringBoot jar包启动内容请搜索以前的⽂章或继续浏览下⾯的相关⽂章希望⼤家以后多多⽀持!

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。