可能有不少初學者會有這樣的困惑(以前我也有過):在你的代碼里調用了一些資源文件,如圖片,音樂等,在調試環境或單獨運行的時候可以正常顯示或播放,而一旦打包到jar文件中,這些東東就再也出不來了,除非把這個jar放到原來未打包以前的目錄下,但通常jar是單獨發布的。這里介紹一個解決這類問題的方法。
getResource和getResourceAsStream
問題的根源還是在于老生常談的所謂class path,不信的話你在系統環境變量里的ClassPath加上你的jar文件,這下你就看得到你的圖片了!但單獨發布jar的話不可能指望每次都讓用戶為你的jar而專門修改classpath。那么有沒有什么辦法一勞永逸地搞定它呢?我們需要從類的裝載入手。先扯遠一點,在開發JSP之類的Web應用程序的時候要用到第三方的庫怎么辦?通常的做法是把這些庫(可以是class,也可以是jar)統統放到WEB-INF/lib/目錄下面,為什么這樣系統就認了呢?因為Web容器(譬如Tomcat)在裝載類的時候有自己的組織方式(可以參考Tomcat手冊http://jakarta.apache.org/tomcat/tomcat-4.1-doc/class-loader-howto.html)。特別地,jar也是類裝載器的一個可訪問媒介,ClassLoader提供了兩個方法用于從裝載的類路徑中取得資源:
public URL getResource(String name);
public InputStream getResourceAsStream(String name);
這里name是資源的類路徑,它是相對與“/”根路徑下的位置。getResource得到的是一個URL對象來定位資源,而getResourceAsStream取得該資源輸入流的引用保證程序可以從正確的位置抽取數據。
真正使用的不是ClassLoader的這兩個方法,而是Class的getResource和getResourceAsStream方法,因為Class對象可以從你的類得到(如YourClass.class或YourClass.getClass()),而ClassLoader則需要再調用一次YourClass.getClassLoader()方法,但根據JDK文檔的說法,Class對象的這兩個方法其實是“委托”(delegate)給裝載它的ClassLoader來做的,所以只需要使用Class對象的這兩個方法就可以了。
在參考資料中有一篇老外寫的文章比較深入介紹了從jar中裝載資源的方法。
一個應用的例子
以下是在我寫的一個小工具MSNHistoryCombiner中用到的一段代碼,可以從jar中裝載圖片和文本信息。譬如,你的jar中根目錄下有個img目錄,里面放有一些圖片,如img1.jpg,你可以這樣調用
Utilities.getImageFromJar("/img/img1.jpg", YourClass.class);
注意必須這里是“/img/img1.jpg”而非“img/img1.jpg”。從jar中讀文本資源也是類似方法調用getTextFromJar。
需要說明的是,這段代碼也不是我原創的,是從一段別的代碼中經過修改得到的,但原代碼的來源忘記了,在這里向原作者表示感謝和歉意。
import java.awt.Image;
import java.awt.Toolkit;
import java.io.*;
public class ResourseReader {
public ResourseReader() {
}
public static Image getImageFromJar(String s, Class class1) {
Image image = null;
InputStream inputstream = class1.getResourceAsStream(s);
if(inputstream != null) {
ByteArrayOutputStream bytearrayoutputstream = new ByteArrayOutputStream();
try {
byte abyte0[] = new byte[1024];
for(int i = 0; (i = inputstream.read(abyte0)) >= 0;) {
bytearrayoutputstream.write(abyte0, 0, i);
}
image = Toolkit.getDefaultToolkit().createImage(bytearrayoutputstream.toByteArray());
}
catch(IOException ioexception) {
ioexception.printStackTrace();
}
}
return image;
}
public static String getTextFromJar(String s, Class class1) {
String s1 = "";
InputStream inputstream = class1.getResourceAsStream(s);
if(inputstream != null) {
BufferedReader bufferedreader = new BufferedReader(new InputStreamReader(inputstream));
String s2;
try {
while((s2 = bufferedreader.readLine()) != null) {
s1 = s1 + s2 + "\n";
}
}
catch(IOException ioexception) {
ioexception.printStackTrace();
}
}
return s1;
}
}
從Jar中讀取gif文件
在Java的程序發布中,很多人會選擇采用二進制的jar的格式進行發布,怎么樣讀取Jar里面的資源呢?
主要是采用ClassLoader的下面幾個方法來實現:
public URL getResource(String name);
public InputStream getResourceAsStream(String name)
public static InputStream getSystemResourceAsStream(String name)
public static URL getSystemResource(String name)
后兩個方法可以看出是靜態的方法,這幾個方法都可以從Jar中讀取圖片資源,但是對與動畫的gif文件,筆者在嘗試過程中發現,存在一些差異。
String gifName為Gif文件在Jar中的相對路徑。
(1)使用了兩個靜態方法
BufferedImage image=ImageIO.read(ClassLoader.getSystemResourceAsStream(gifName));
或者
Image image=Toolkit.getDefaultToolkit().getImage(ClassLoader.getSystemResource(gifName));
這兩種方式可以成功地讀取gif文件,但是對于gif動畫,顯示出來地是靜態的。
(2)使用其他兩個方法
Image image=Toolkit.getDefaultToolkit().getImage(this.getClass.getClassLoader().getResource(gifName));
再這種方式下動畫可以正常顯示了。