SpringBoot打包后的Jar的加载进制(java -jar xxx.jar)

首先,咱们使用maven插件的打包功能,对总项目进行打包,成功后,子项目都生成了相应的xxx-SNAPSHOT.jar的jar文件:
在这里插入图片描述
将含有main方法的admin-system对应的jar包文件复制到本地的一个目录下,然后对齐进行解压缩,出现的几个目录:
在这里插入图片描述
我们就先看BOOT-INF\classes下的内容,其实就是我们项目编写的代码内容:
在这里插入图片描述
BOOT-INF\lib下的内容也就是,咱们pom.xml里面进行依赖导入的一些jar包,又或者是咱们mavem的repository目录下的jar,反正很多jar包…
在这里插入图片描述
看完BOOT-INF下的目录内容,大概就是和咋们的项目内容差不多,然后再看下META-INF目录下的内容,里面有个MANIFEST.MF文件,点开查看会有一些信息,标红的内容是后面分析源码的时候需要使用的
在这里插入图片描述
接着我们再看org下面的内容,对应的是一些.class字节码文件,通过查看咱们pom.xml文件可以发现,好像我们并没有导入org.springframework.boot.loader这个依赖,而打成Jar包后就有了这个内容了:
在这里插入图片描述
为了分析Jar包这里多出来的依赖的内容,咱们就在自己项目的pom.xml里加入这个依赖:
在这里插入图片描述
然后就可以看到IDEA项目中也有了Jar包中所出现的org.springframework.boot.loader的目录内容:
在这里插入图片描述
我们现在就主要分析JarLauncher这个类,因为这个类有main方法:
在这里插入图片描述
点进这个方法后,可以通过doc文档(注释)分析这个方法:
Launch the application. This method is the initial entry point that should be called by a subclass {@code public static void main(String[] args)} method.
启动应用,这个方法是初始入口点,应该被子类的main方法调用。
在这里插入图片描述

JarFile.registerUrlProtocolHandler();

这行代码就是将java.protocol.handler.pkgs这个参数注册进去,这样URLStreamHandler就可以定位到jar URLs,然后处理他们(不太重要)。


ClassLoader classLoader = createClassLoader(getClassPathArchives());

重点是这行代码,可以看出结果是生成了一个类加载器。
先来分析下getClassPathArchives()这个方法的作用,点击去就来到了JarLaucher的父类ExecutableArchiveLaucher的这个方法:
在这里插入图片描述
可以看出是将archive(档案)转成了一个List,然后返回了这个处理后的archive,所以我们先看看ExecutableArchiveLaucher的这个archive属性是什么含义:
在这里插入图片描述
可以看见这个archive在ExecutableArchiveLaucher的构造方法时通过createArchive()这个方法进行了赋值,这个createArchive()的内容:
在这里插入图片描述
大概就是返回这个Laucher类对应的ProtectionDomain保护域的File(文件或目录),就目前来看,我们就直接把这个archive当做Jar包里所有的内容吧,包括文件和目录,因为后面会有对archive遍历是否含有内嵌的内容,应该是对整个Jar进行遍历。


我们再回来,可以知道这个archive大致的内容了,我们再来看看这个getNestedArchives()这个方法:
在这里插入图片描述
这个getNestedArchives()的参数是一个isNestedArchive方法的Stream操作,isNestedArchive的doc:
Determine if the specified {@link JarEntry} is a nested item that should be added to the classpath. The method is called once for each entry.
@param entry the jar entry
@return {@code true} if the entry is a nested item (jar or folder)
确定指定的JarEntry是否是需要被加载classpath的内嵌条目,每个entry都会调用一次这个方法。
参数是 jar entry(jar文件)
返回这个jar文件或者目录是否是内嵌的

点下去可以发现对应的操作就是返回archives(档案)中的所有的内容(文件或目录)路径是否是等于"BOOT-INF/classes/"或者以"BOOT-INF/lib/"开头:
在这里插入图片描述
至于getNestedArchives()方法的具体操作,通过方法意思也就可以知道,就是拿到archives(档案)中的所有的内容(文件或目录)路径是否是等于"BOOT-INF/classes/"或者以"BOOT-INF/lib/"开头的内嵌内容,然后将这些内容返回成一个List。


到这里,我们可以先了解下,为什么这里需要拿到"BOOT-INF/classes/"和以"BOOT-INF/lib/"下的内嵌内容呢?
根据Jar文件标准,对于Main-Class对应的包内容必须是在顶层目录结构中,上面的MANIFEST.MF文件图可以发现Main-Class对应的内容是:org.springframework.boot.loader.JarLauncher,这个对应的就是整个Jar包的启动类(可以想到这个类是由AppClassLoader加载),他呢对应的包内容即org\springframework\boot\loader…,必须是位于Jar包的顶层目录上的,即不能被嵌套的,通过对Jar的解压缩我们也可以看出这个包是位于Jar的顶层目录上的:
在这里插入图片描述
这也是为什么打包的时候不会把org.springframework.boot.loader对应的内容生成Jar包,如果是将其生成Jar包放入Jar包里,就会形成一种嵌套的关系,对应的目标类Main-Class是无法被执行的,所以将整个内容拷贝到Jar里面。
也就是说BOOT-INF里的内容是不符合Jar标准的,即里面的内容是无法被执行的,即使里面含有main方法的启动类:
在这里插入图片描述
那这个项目是要运行的,这个启动类肯定是需要加载、连接、初始化的呀,在SpringBoot里创建了一个自定义的类加载器,用来专门加载"BOOT-INF/classes/"和以"BOOT-INF/lib/"的内容,这也是为什么上面的一堆方法getNestedArchives()、isNestedArchive()等,就是为了拿到这两个目录下的内容的原因!
题外话,对于传统的打包方式,项目中会有很多的依赖jar文件,而这些jar文件也以jar文件的方法被打包到项目的jar中,那么这些依赖jar文件在项目jar包中是无法被加载的,所以传统的方式就是在项目打包的时候,把这些依赖的jar文件内容和项目中自己写的内容拷贝到jar中,也就是将依赖的jar文件给解压缩后的内容连同自己写的代码一同打包到总的项目jar中,这样就造成了项目jar包中,即有自己写的代码,还有依赖jar包里的具体代码,这使得项目jar包的内容比较混乱,而且如果有类名相同的,打包就会出现覆盖,一个副覆盖另一个。


再回来后面的代码,
在这里插入图片描述
postProcessClassPathArchives():
Called to post-process archive entries before they are used. Implementations can add and remove entries.
@param archives the archives
@throws Exception if the post processing fails
这个方法被调用,在 archive entriees被使用前,post-process一下,至于具体实现,方法并没有:
在这里插入图片描述
到这里可以知道getClassPathArchives()大概就是返回了"BOOT-INF/classes/"和以"BOOT-INF/lib/"下面的archive内容,现在再分析createClassLoader():
在这里插入图片描述
通过这个方法的doc,可以知道
Create a classloader for the specified archives.
@param archives the archives
@return the classloader
@throws Exception if the classloader cannot be created
为指定的archives创建一个类加载器
这个方法内容主要是创建一个类加载器来加载archives对应的URL路径 ,
重点在于createClassLoader()里面的createClassLoader()
在这里插入图片描述
点进去是其父类Luacher类的createClassLoader()的具体实现:
在这里插入图片描述
其doc注释可以看出,这个方法是为指定的URLS创建类加载其,而URLS其实是Archive类子类JarFileArchive类的一个属性,对应的就是archive的内容目录或jar文件路径,而代码中getClass().getClassLoader(),这个指的是当前类对象的获取类加载器的方法,很容易知道,这些内容都是外部导入的依赖,都是由AppClassLoader来加载的,这个方法创建了LaunchedURLClassLoader进行返回,很明显这是一个类加载器,是SpringBoot提供的一个全新的类加载器。 而通过张龙老师的JVM课程知道,一个类加载必须要声明他的父加载器,因为双亲委托机制…,所以这个LaunchedURLClassLoader类的父加载器是AppClassLoader,点进去这个类也可以知道,传入的第二个参数就是LaunchedURLClassLoader对应的父加载器:在这里插入图片描述
LaunchedURLClassLoader这个类他是 used by the {@link Launcher}.被Laucher用来使用的,用来加载类,而这个类的父类是URLClassLoader:
在这里插入图片描述
而URLClassLoader又是java.net包下面的一个类加载器了
在这里插入图片描述
其doc文档:
This class loader is used to load classes and resources from a search path of URLs referring to both JAR files and directories. Any URL that ends with a ‘/’ is assumed to refer to a directory. Otherwise, the URL is assumed to refer to a JAR file which will be opened as needed.
这个类加载器是用来加载查找引用JAR文件或目录的URLs对应的类和资源。嘉定所有以’/'结尾的URL都被认为是一个目录,否则就假设这个URL引用的是一个需要被打开的JAR文件。
再往上就jdk自己的类加载器对应的处理了,到这里我们可以知道是SpringBoot提供的专门用来加载"BOOT-INF/classes/"和"BOOT-INF/lib/"目录下面的JAR包和类文件。


我们再回来,我们已经拿到了LaunchedURLClassLoader,现在分析后面的操作,其实也可以猜到,就是用LaunchedURLClassLoader来加载"BOOT-INF/classes/"和"BOOT-INF/lib/"目录下面的JAR包和类文件。
LaunchedURLClassLoader
其中的getMainCLass():
在这里插入图片描述
这个方法的doc:
Returns the main class that should be launched.
@return the name of the main class
@throws Exception if the main class cannot be obtained
返回应该被启动的主类
其中方法的具体内容中的Manifest类,对应的Jar包解压缩后的MANIFEST.MF文件,且方法也指明了需要Start-Class这个值:
在这里插入图片描述
所以getMainClass()返回的是com.myproject.admin.AdminSystemApplication字符串
再回来看launch(args, getMainClass(), classLoader);
大概的意思就是用Springboot创建的LaunchedURLClassLoader去加载com.myproject.admin.AdminSystemApplication这个启动类。
我们再点进去这个方法看下:
在这里插入图片描述
doc:
Launch the application given the archive file and a fully configured classloader.
启动给定的archive文件应用,和被配置的类加载器
Thread.currentThread().setContextClassLoader(classLoader);这行是将线程上下文类加载器设为LaunchedURLClassLoader(线程上下文加载器默认是AppClassLoader ),
createMainMethodRunner(mainClass, args, classLoader).run();这行代码就是用LaunchedURLClassLoader来加载com.myproject.admin.AdminSystemApplication,并调用他的mian方法:
在这里插入图片描述
MainMethodRunner的doc:
Utility class that is used by {@link Launcher}s to call a main method. The class
containing the main method is loaded using the thread context class loader.
被Laucher使用的辅助类,用来调用main方法。包含main方法的这个类。
mainMethod.invoke(null, new Object[] { this.args });传入的对象是null,因为main方法是static,staitc方法不属于类,而是属于这个类对应的class对象。
(com.myproject.admin.AdminSystemApplication)用来被线程上下文加载器加载。
至此差不多就可以清楚了Jar包的执行过程了!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值