天天看點

java:讀取jar包中配置檔案的幾種方式

文章目錄

    • 概述
    • 定義接口
    • 通過JarFile讀取
    • 通過URL讀取
    • 通過ClassLoader
    • 總結

概述

在程式設計的某些情況下,我們需要讀取jar包中的檔案,這種情況要差別于我們平時使用類加載器讀取配置檔案,這個時候配置在jar包中,就能讀取到,但是配置檔案也可以不在jar包中,隻要放在Class-Path下就行了,是以這種情況下,我更願意把它稱之為:讀取Class-Path下的配置檔案。而我今天描述的比較明确,就是要讀取jar包中的檔案。這種需求可能不多,但是我碰見了,并且發現了幾種,今天全部羅列分享一下。

目前有3種:

  1. JarFile
  2. URL
  3. ClassLoader

定義接口

因為有好幾種方式,那就直接定義個接口:

public interface JarReader {
   /**
     * 讀取jar包中的檔案
     * @param jarPath   jar包路徑
     * @param file  jar包中的檔案路徑
     * @return  檔案内容,轉換成字元串了,其它需求也可以轉換成輸入流。
     * @throws IOException
     */
    String readFromJar(String jarPath,String file) throws IOException;
}
           

jar包讀取器,jar包中的檔案讀取出來。

通過JarFile讀取

JarFile是java自帶的一種讀取jar包的API,很多人應該用過,我就直接貼代碼了。

public class JarFileJarReader implements JarReader {

    @Override
    public String readFromJar(String jarPath,String file) throws IOException {
        JarFile jarFile=null;
        try {
            jarFile=new JarFile(jarPath);
            JarEntry jarEntry=jarFile.getJarEntry(file);
            InputStream input=jarFile.getInputStream(jarEntry);
            return IOUtils.toString(input,"UTF-8");
        } catch (IOException e) {
            throw e;
        } finally {
            IOUtils.closeQuietly(jarFile);
        }
    }
}
           

代碼也比較簡單,重點就是最後一定要把jarFile這個對象關閉一下,中間的輸入流都可以不用關閉。

不過我在寫這段代碼之前,從我的個人經驗上來說,JarFile好像更多是用來讀取清單檔案(MANIFEST.MF)的,可能是見這種情況比較多,當然它的用途肯定遠不止如此。

是以我順便寫了一下讀取清單檔案的代碼:

public void getManiFest(String jarPath) throws IOException {
        JarFile jarFile=null;
        try {
            jarFile=new JarFile(jarPath);
            Manifest manifest=jarFile.getManifest();
            if (manifest!=null){
                //擷取Class-Path
                String classPaths = (String) manifest.getMainAttributes().get(new Attributes.Name("Class-Path"));
                if (classPaths != null && !classPaths.isEmpty()) {
                    String[] classPathArray = classPaths.split(" ");

                }
                //擷取JDK版本
                String jdkVersion = (String) manifest.getMainAttributes().get(new Attributes.Name("Build-Jdk"));

                //還可以擷取其它内容,比如Main-Class等等
            }

        } catch (IOException e) {
            throw e;
        } finally {
            IOUtils.closeQuietly(jarFile);
        }
    }
           

通過URL讀取

java自帶的URL是支援讀取jar中的檔案的,協定是jar,表示方式的話是用"/!"把jar包和檔案區分一下。代碼如下:

public class URLJarReader implements JarReader {
    @Override
    public String readFromJar(String jarPath, String file) throws IOException {
        JarURLConnection jarURLConnection=null;
        try {
            URL fileUrl=ParseUtil.fileToEncodedURL(new File(jarPath));
            URL jarUrl=new URL("jar", "", -1, fileUrl + "!/");
            URL moduleUrl = new URL(jarUrl, ParseUtil.encodePath(file, false));
            jarURLConnection = (JarURLConnection)moduleUrl.openConnection();
            return IOUtils.toString(jarURLConnection.getInputStream(),"UTF-8");
        } catch (IOException e) {
            throw e;
        } finally {
            if (jarURLConnection!=null){
                try {
                    jarURLConnection.getJarFile().close();
                } catch (IOException ignore) {
                }
            }
        }
    }
}
           

ParseUtil的幾個方法是我在看java源碼的時候看見的,用來處理一些不規則的檔案路徑。

我剛開始用URL的時候,就出現了一個記憶體洩漏的檔案,讀取完了以後,jar包被占用,死活不能删除,剛才開始把輸入流給關閉了,也沒有用。然後想到了類加載器裡面有close方法,然後去看了一下,找到了上述代碼中的finally塊的代碼。這樣就可以把占用問題解決了,仔細看的話,會發現,getJarFile.close(),是以本質上還是關閉了JarFile,和上面是一樣的。

通過ClassLoader

這個也是借鑒了我們平時讀取配置檔案的方式,借用一下ClassLoader來讀取。

public class ClassLoaderJarReader implements JarReader {
    @Override
    public String readFromJar(String jarPath, String file) throws IOException{
        URLClassLoader urlClassLoader=null;
        try {
            URL fileUrl=ParseUtil.fileToEncodedURL(new File(jarPath));
            urlClassLoader=new URLClassLoader(new URL[]{fileUrl},null);
            InputStream inputStream=urlClassLoader.getResourceAsStream(file);
            if (inputStream==null){
                throw new FileNotFoundException("not find file:"+file+" in jar:"+jarPath);
            }else{
                return IOUtils.toString(inputStream,"UTF-8");
            }
        } catch (IOException e) {
            throw e;
        } finally {
            IOUtils.closeQuietly(urlClassLoader);
        }
    }
}
           

代碼也是比較簡單,最後把ClassLoader關閉一下就行了。

關于類加載器的讀取方式,我很早之前就看過了,它本質上用的就是上面兩種方式結合起來讀取檔案的。

總結

這幾種方式的話,其實沒有什麼差別,從開發角度來說的話,比較推薦第三種,因為是java自帶的功能,也是比較完善,也簡單,也不容易出錯,而且它内部用的就是前面兩種。不過從資源消耗上面來說,我猜測,前面兩種應該占優,不過我也不糾結這個,沒去研究。

有時候我們或許有另外一種需求,讀取jar中的jar中的檔案,這個在一些場景下,會使用到。最起碼spring-boot确實是用到了,很早之前我看過它的實作,它就是把URL重寫了一下,支援了一下多個"/!"表達式,就能夠支援這種情況了。