当前位置: 首页 > 后端技术 > Java

SpringBoot内嵌JAR免解压加载原理

时间:2023-04-01 14:56:41 Java

SpringBoot内嵌JAR免解压加载原理原因由于项目环境基于Felix(apache开源的OSGi实现框架),当Felix框架处理包含内嵌JAR包的组件时,需要toEmbeddedJARs必须解压到本地缓存目录才能访问这些嵌入式JAR资源。由于内嵌的JAR较多,会占用500M左右的重复磁盘空间。一个包含嵌入式JAR的OSGi组件是这样的:回想一下,Springboot在打包的时候,也可以把项目的所有依赖打包成一个JAR,直接以java-jartest.jar的形式运行。这给项目的分发带来了极大的方便。SpringBoot单个JAR文件的目录结构与OSGi内嵌JAR结构类似:其中BOOT-INF/classes、BOOT-INF/lib/*.jar是我们应用的classes-path中的内容。我们观察到,SpringBoot运行时无需将lib下的jar包解压到本地磁盘目录,就可以直接访问内嵌JAR中的类。如何才能做到这一点?探究是因为Java是按需加载类的,即:在应用程序启动过程中,不会也没有必要将JAR包中的class字节全部读入内存;但是随着程序的运行,当需要某个类的功能时,会从class-path中查找该类的字节码,然后加载到内存中。我们知道,从文件存储格式来看,JAR文件本质上就是Zip文件格式(只是JAR约定了META-INF/MANIFEST.MF文件必须是压缩包的第一个入口)。那么,为什么Zip文件可以实现Entry的随机读取呢?这取决于Zip文件的存储结构。Zip文件按条目数据顺序堆叠。在Zip文件的末尾,存放了所有的Entry目录信息,标识了每个Entry在Zip文件中的偏移位置。由于Zip文件是针对每个Entry单独压缩的(每个Entry都可以选择是否压缩,以及压缩方式),而不是将所有的Entry一起压缩——这一点很重要!因此,可以通过Entry目录定位任意Entry,从而实现对Entry数据的随机读取。再来看JDK的Zip相关API。JDK使用自己的类java.util.zip.ZipFile实现随机读取JAR中的类或资源。该类需要一个本地的File作为构造函数参数,不支持读取内嵌JAR包中的类资源。SpringBoot的实现spring-boot-loader是SpringBoot的bootstrap程序。使用Maven-Install打包SpringBoot单JAR包时,会将SpringBootLoader的代码复制到JAR中。org.springframework.bootspring-boot-loader2.6.6SpringBoot单个JAR包MANIFEST内容:Manifest-版本:1.0实现标题:SpringBootDemoSpring-Boot-版本:2.0.6.RELEASEMain-Class:org.springframework.boot.loader.JarLauncherStart-Class:springboot2.DemoAppSpring-Boot-Classes:BOOT-INF/classes/Spring-Boot-lib:BOOT-INF/lib/我们看到SpringBootLoader:接管了SpringBoot程序的启动入口(Main-Class),实现了JDK中java.util.zip.ZipFile的扩展,支持类的随机化内嵌的JARAccess提供了SpringBoot-ClassLoader负责BOOT-INF下classes和lib的资源加载(Spring-Boot-Classes,Spring-Boot-Lib内容构成了应用真正的class-path)使用SpringBootLoader的Zip扩展随机读取内嵌JAR资源我们封装了一个SpringBootJar,如果名字是:SpringBootDemo-1.0-boot.jar,其中在BOOT-INF/lib目录下有一个内嵌JAR:logback-core-1.2.3.jar,我们试试读取这个嵌入式JAR中的class文件:ch.qos.logback.core.Appender.class示例代码如下:importorg.springframework.boot.loader.jar.JarFile;公开课Demo{publicstaticvoidmain(String[]args)throwsException{JarFilerootJar=newJarFile(newFile("SpringBootDemo-1.0-boot.jar"));StringinnerName="BOOT-INF/lib/logback-core-1.2.3.jar";StringclassName="ch.qos.logback.core.Appender";ZipEntry条目=rootJar.getEntry(innerName);JarFileinnerJar=rootJar.getNestedJarFile(条目);StringclassEntryName=className.replace('.','/')+".class";entry=innerJar.getEntry(classEntryName);InputStreamin=innerJar.getInputStream(入口);byte[]bs=newbyte[(int)entry.getSize()];对于(inti=0;i