读取嵌套jar包中的文件

读取jar包中的jar 文件

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

A.jar
    |--B.jar
    |--Test.class
    |--.....

经过 new JarFile(A.jar) 能够等到A.jar 对应的对象,能够遍例A.jar中的全部文件,Jar包中的文件以 JarEntry的形式保存数据 ,全码大体以下:git

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 的运行过程github

对应的java类的大体说明

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

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

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

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

    这是一个抽象类,做用以下dom

    1. 建立具体的 Archive 实例( Archive createArchive()),JarFileArchive 仍是WarFileArchive,具体是经过class文件的协议名来断定具体实例了。若是jar包启动,class 文件url前面的协议是以 jar:file开头的; 若是是war包,由于窗口会将war解压以后 再启动,因此class文件url的协议是file://spring-boot

    2. 建立上下文的ClassLoader;用于加载嵌套包中的class 与 classes文件夹中的class。为何要设置上下文classLoader呢?由于启动springboot 的jar包时的classpath 只有jre环境与 springboot 的jar包,若是用启动Launcher的ClassLoader会找不到类,因此要设置上下文ClassLoader 为LanuchedURLClassLoaderthis

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

  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包中每一个文件的流的偏移量,文件大小等这些信息

2、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工程,其余工程有没有可能也使用这种方式进行打包并进行任意层的嵌套呢?感受好蠢的想法

参考: