读取嵌套jar包中的文件

编程

读取jar包中的jar 文件

例如有一个Jar包 A.jar,他的目录文件如下图

A.jar

|--B.jar

|--Test.class

|--.....

通过 new JarFile(A.jar) 可以等到A.jar 对应的对象,可以遍例A.jar中的所有文件,Jar包中的文件以 JarEntry的形式保存数据 ,全码大致如下:

     public void testJar() throws IOException {

JarFile jarFile = new JarFile("C:\Users\Mzoro\Desktop\operation-1.1.jar");

System.out.println(jarFile.getName());

Enumeration<JarEntry> entries = jarFile.entries();

while (entries.hasMoreElements()) {

JarEntry entry = entries.nextElement();

String name = entry.getName();

System.out.println(entry.getAttributes());

System.out.println(name);

}

}

但是 如果想继续遍历B.jar中的文件就不行了,需要其他方法,有一个活生生的例子是 spring-boot 打包后的jar 的运行过程

对应的java类的大致说明

一、嵌套jar的数据与信息获取方面

  1. Archive,对jar包,或者目录的抽象

    对jar包的抽象就是常见的,将spring-boot 工程发布成可执行jar 包,和嵌套其中的jar包与或目录,具体实现是org.springframework.boot.loader.archive.JarFileArchive;可以通过JarFileArchive 实例获取它的子目录或者嵌套的jar

  2. org.springframework.boot.loader.Launcher,真正的springboot启动类

    这是一个抽象类,作用如下

    1. 创建具体的 Archive 实例( Archive createArchive()),JarFileArchive 还是WarFileArchive,具体是通过class文件的协议名来判定具体实例了。如果jar包启动,class 文件url前面的协议是以 jar:file开头的; 如果是war包,因为窗口会将war解压之后 再启动,所以class文件url的协议是file://

    2. 创建上下文的ClassLoader;用于加载嵌套包中的class 与 classes文件夹中的class。为什么要设置上下文classLoader呢?因为启动springboot 的jar包时的classpath 只有jre环境与 springboot 的jar包,如果用启动Launcher的ClassLoader会找不到类,所以要设置上下文ClassLoader 为LanuchedURLClassLoader

    3. 这个类声明了一个 abstract List<Archive> getClassPathArchives() 方法,抽象的,目的是返回ClassPath 下的jar包或者目录,为什么设置为abstract呢?因为 war与jar 的运行时依赖的lib 是在不同目录下的,class 也在不同目录下,同时还需要过滤掉一些不必要的jar 包或者war包中的东西,比如MANIFEST.MF 文件对加载类是没有用的,所有Archive 集合中没有必要包含它。这个方法的返回值会在构造LancherURLClassLoader时传入,在findClass时 会在这些Archive 代表的目录或者文件中查找Class文件

  3. org.springframework.boot.loader.jar.JarFile

    这个类继承自 java.util.jar.JarFile, 主要重写的方法 Enumeration<java.util.jar.JarEntry> entries(); 它对应的是springboot jar中嵌套的jar ,这个类的主要作用是在构造时创建一个JarFileEntries,这个类主要重写了entries()方法,而这个方法返回的Enumeration 是依靠JarFile持有的JarFileEnties获得的

    private JarFile(RandomAccessDataFile rootFile, String pathFromRoot,

    RandomAccessData data, JarEntryFilter filter, JarFileType type)

    throws IOException {

    super(rootFile.getFile());

    this.rootFile = rootFile;

    this.pathFromRoot = pathFromRoot;

    CentralDirectoryParser parser = new CentralDirectoryParser();

    this.entries = parser.addVisitor(new JarFileEntries(this, filter));

    parser.addVisitor(centralDirectoryVisitor());

    this.data = parser.parse(data, filter == null);

    this.type = type;

    }

  4. org.springframework.boot.loader.jar.JarFileEntries

    这个类的作用非常重要,它代表一个jar包中的所有Entries,并且这个类在构建时就保存了这个jar包中所有Entry的文件流信息,所有在通过这个类的对象获取具体的JarEnty对象时,JarEnty对象就可以包含entry对应的文件的真正的流数据。在definedClass方法的入参,byte[]是一个必须的参数

    个人觉得难就难在这里,如何计算jar包中每个文件的流的偏移量,文件大小等这些信息

    • 压缩包文件格式

    • 压缩包文件官方说明

    • 压缩包文件wiki(英)

二、ClassLoader方面

  1. LaunchedURLClassLoader

    它继承自URLClassLoader,这个类相对LaunchedURLClassLoader 没有太大区别,主要的区别在于对包的定义,因为在定义包时要从嵌套jar 中获取MANIFEST.MF 信息

  2. org.springframework.boot.loader.jar.Handler

    因为 URLClassLoader在获取Class文件时需要通过 URL对象来获取,而这个url具体如何获取(或者说打开Connection),可以指定Handler,org.springframework.boot.loader.jar.Handler就是为了打开嵌套jar 连接延生的; 它是实现了java.net.URLStreamHandler的类,URLStreamHandelr只有一个抽象方法,就是URLConnection openConnection(URL url)

  3. JarURLConnection

    可以通过这个类获取 InputStream了,有了InputStream 就可以等到 definedClass所需的byte[]参数,而这个JarURLConnection获取InputStream的方法是通过构建 JarURLConnection时的 JarFile来获取的,JarFile获取InputStream 的方法是通过其持有的JarFileEntries来获取的 ,JarFileEntries的获取方法就是读取jar 包的偏移量读取二进制数据

总结

看了一通代码最后感觉还是不能自己实现,难点在于读取嵌套jar包流的问题上在

疑问

代码上感觉spring-boot-loader只处理了一层嵌套,不知道能不能处理多层的,当然,可能也没有人这么用;如果可以的话,那么除了springboot工程,其他工程有没有可能也使用这种方式进行打包并进行任意层的嵌套呢?感觉好蠢的想法

参考:

  • spring-boot地址

  • springboot 加载class方法

  • 压缩包文件格式

  • 压缩包文件官方说明

  • 压缩包文件wiki(英)

以上是 读取嵌套jar包中的文件 的全部内容, 来源链接: utcz.com/z/514455.html

回到顶部