讀取嵌套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工程,其餘工程有沒有可能也使用這種方式進行打包並進行任意層的嵌套呢?感受好蠢的想法

參考:

相關文章
相關標籤/搜索