深入Jar包:Gradle構建可執行jar包與訪問jar包中文件夾與文件

前言

Java的跨平台功能聽起來很誘人可口,號稱「Write Once,Run Everywhere」,實際上是「Run Once,Debug Everywh」… 在實際開發過程中還是會遇到各種各樣的坑的,剛剛解決了一系列問題,特地寫個文章總結一下。

使用Gradle構建Jar包

感謝萬能的Gradle,極大提高了Java開發的生產力~
在Gradle中生成jar包可以使用官方的插件:application 來簡單生成Jar包,同時還有多種不同的配置可以自定義,了解詳情請參照Gradle官方文檔。

我這裡使用的是一個叫做 shadow 的Gradle插件,把構建jar包的配置都安排得明明白白了,非常的方便!
官方文檔://imperceptiblethoughts.com/shadow/configuration/#configuring-output-name

下面是 build.gradle 配置參考:

plugins {
    id 'com.github.johnrengelman.shadow' version '4.0.3'

    // Apply the java plugin to add support for Java
    id 'java'

    // Apply the application plugin to add support for building an application
    id 'application'
}

dependencies {
    implementation 'com.github.jengelman.gradle.plugins:shadow:4.0.3'
}

// Output to build/libs/name.jar
shadowJar {
    baseName = 'name'
    classifier = null
    version = null
}

apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: 'java'

具體的配置要依照項目的實際需要來配置~
設置完 shadow 插件之後,執行 gradle build 就可以在 build/libs/ 文件夾下面生成你的可執行jar包了,超級方便。
需要更多功能可以查看shadow官網文檔,寫的很清楚。

訪問jar包中的資源

雖然jar包中有各種目錄結構,但是jar包本質仍然是一個文件,所以不可以用傳統的方法去訪問,像 File 類,Class 對象的 getResouce 方法都不行的。
應該使用 ClassLoadergetResourceStream 方法直接獲取資源文件的輸入流。
例如:

InputStream is=this.getClass().getResourceAsStream("/resource/res.txt");
InputStream is=this.getClass().getClassLoader().getResourceStream("/resource/res.txt");

注意:Class對象和ClassLoader對象的getResourceStream方法也是有不同的,具體的不同可以查看這個筆記:正確獲取Java項目資源

訪問Jar包中的文件夾

當jar包中的資源文件很多的時候,不可能一個個輸入名字去獲取,這也太hack了吧,肯定要用自動化的方式來提高生產力。
事實上,訪問jar包中的文件夾是挺麻煩的,不過還是找到了取巧的方法,試了一下還是挺好用的。
(不過最好做一下快取)

程式碼如下:

String path = getClass().getProtectionDomain().getCodeSource().getLocation().getPath();
JarFile localJarFile = new JarFile(new File(path));

Enumeration<JarEntry> entries = localJarFile.entries();
while (entries.hasMoreElements()) {
      JarEntry jarEntry = entries.nextElement();
      String innerPath = jarEntry.getName();
      System.out.println(innerPath);
}

使用getClass().getProtectionDomain().getCodeSource().getLocation().getPath(); 來獲取當前jar包的路徑,如果程式碼不在jar包中運行的話,獲取到的就是當前class文件所在路徑。所以在使用之前最好做一下判斷,看看程式是否在jar包中運行。

關於JavaFX的Media資源問題

JavaFX可以播放音樂,但是和其他Image、Font資源不同的是,Media對象的構造函數只能接受一個String參數(即文件URL),所以沒辦法使用getResourceStream方法把文件輸入流傳入對象。

我查了一下官網,找到了解決辦法,把文件URL換成JarURL就可以了,文檔://docs.oracle.com/javase/6/docs/api/java/net/JarURLConnection.html。

簡單示例:

String path = String.format("jar:file:%s!/%s", jarPath, relativePath);
Media media = new Media(path);

注意:relativePath的形式是 media/hello.wav 這樣的。

Tags: