zoukankan      html  css  js  c++  java
  • 读取Jar包中的资源问题探究

    最近在写一个可执行jar的程序,程序中包含了2个资源包,一个是images,一个是files。问题来了,在Eclipse里开发的时候,当用File类来获取files下面的文件时,没有任何问题。但是当程序导出为Runnable Jar时,运行程序时抛出异常,File not found。我们来一探究竟。

    首先是我的工程目录结构:

    程序中访问images下的图片代码:

    1. setIconImage(new ImageIcon(this.getClass().getResource(  
    2.                 "/images/icon.png")).getImage());  

    结果,在Eclipse中和导出的jar包,运行正常。注意此处没有使用File类。

    程序中用File类访问files文件夹下的资源代码:

    1. File f = new File(InfoUtils.class.getClass().getResource("/files/news.ini").getPath());  

    结果,在Eclipse中运作正常,导出的jar包,抛异常。

    然后尝试了如下的方法在jar包中来获取news.ini,结果宣告失败。

    1. System.out.println("*************path test**************");  
    2.             System.out.println(InfoUtils.class.getResource("").getPath());  
    3.             System.out.println(InfoUtils.class.getResource("/").getPath());  
    4.             System.out.println(InfoUtils.class.getClassLoader().getResource(""));  
    5.             System.out.println(new File("/files").getAbsolutePath());  
    6.             System.out.println(new File("/files").getPath());  
    7.             System.out.println(new File("/files").getPath());  
    8.             System.out.println(new File("").getAbsolutePath());  
    9.             System.out.println(new File("").getCanonicalPath());  
    10.             System.out.println(System.getProperty("java.class.path"));  
    11.             System.out.println("*************path test**************");  

    我们来分析下,主要是因为jar包是一个单独的文件而非文件夹,绝对不可能通过"file:/e:/.../xxx.jar/files /news.ini"这种形式的文件URL来定位news.ini。所以即使是相对路径,也无法定位到jar文件内的ini文件(读者也许对这段原因解释有些费解,在下面我们会用一段代码运行的结果来进一步阐述)。

    那么把资源打入jar包,无论ResourceJar.jar在系统的什么路径下,jar包中的字节码程序都可以找到该包中的资源。这会是幻想吗?当然不是,我们可以用类装载器(ClassLoader)来做到这一点:

    (1) ClassLoader是类加载器的抽象类。

    它可以在运行时动态的获取加载类的运行信息。 可以这样说,当我们调用ResourceJar.jar中的Resource类时,JVM加载进Resource类,并记录下Resource运行时信息(包括Resource所在jar包的路径信息)。而ClassLoader类中的方法可以帮助我们动态的获取这些信息:
             ● public URL getResource(String name) 
               查找具有给定名称的资源。资源是可以通过类代码以与代码基无关的方式访问的一些数据(图像、声音、文本等)。并返回资源的URL对象。
             ● public InputStreamgetResourceAsStream(String name); 
               返回读取指定资源的输入流。这个方法很重要,可以直接获得jar包中文件的内容。

    (2) ClassLoader是abstract的,不可能实例化对象,更加不可能通过ClassLoader调用上面两个方法。所以我们真正写代码的时候,是通过Class类中的getResource()和getResourceAsStream()方法,这两个方法会委托ClassLoader中的getResource()和getResourceAsStream()方法 。好了,现在我们重新写一段Resource代码,来看看上面那段费解的话是什么意思了:

    1.     
    2. import java.io.*;    
    3. import java.net.URL;    
    4. public class Resource {    
    5.     public  void getResource() throws IOException{      
    6.               //查找指定资源的URL,其中res.txt仍然开始的bin目录下     
    7.         URL fileURL=this.getClass().getResource("/resource/res.txt");     
    8.         System.out.println(fileURL.getFile());    
    9.     }    
    10.     public static void main(String[] args) throws IOException {    
    11.         Resource res=new Resource();    
    12.         res.getResource();    
    13.     }    
    14. }    


    运行这段源代码结果:/E:/Code_Factory/WANWAN/bin/resource/res.txt  (../ Code_Factory/WANWAN/.. 是javaproject所在的路径)。

    我们将这段代码打包成ResourceJar.jar,并将ResourceJar.jar放在其他路径下(比如 c:ResourceJar.jar)。然后另外创建一个javaproject并导入ResourceJar.jar,写一段调用jar包中Resource类的测试代码:

    1. import java.io.IOException;    
    2. import xx.Resource;    
    3. public class TEST {    
    4.     public static void main(String[] args) throws IOException {    
    5.         Resource res=new Resource();    
    6.         res.getResource();    
    7.     }    
    8. }    

    这时的运行结果是:file:/C:/ResourceJar.jar!/resource/res.txt。

    我们成功的在运行时动态获得了res.txt的位置。然而,问题来了,你是否可以通过下面这样的代码来得到res.txt文件?

    File f=newFile("C:/ResourceJar.jar!/resource/res.txt");
    当然不可能,因为".../ResourceJar.jar!/resource/...."并不是文件资源定位符的格式(jar中资源有其专门的URL形式:jar:<url>!/{entry} )。所以,如果jar包中的类源代码用Filef=newFile(相对路径);的形式,是不可能定位到文件资源的。这也是为什么源代码1打包成jar文件后,调用jar包时会报出FileNotFoundException的症结所在了。

    (3) 我们不能用常规操作文件的方法来读取ResourceJar.jar中的资源文件res.txt,但可以通过Class类的getResourceAsStream()方法来获取 ,这种方法是如何读取jar中的资源文件的,这一点对于我们来说是透明的。我们将Resource.java改写成:

    1. import java.io.*;    
    2. public class Resource {    
    3.     public void getResource() throws IOException{    
    4.         //返回读取指定资源的输入流    
    5.         InputStream is=this.getClass().getResourceAsStream("/resource/res.txt");     
    6.         BufferedReader br=new BufferedReader(new InputStreamReader(is));    
    7.         String s="";    
    8.         while((s=br.readLine())!=null)    
    9.             System.out.println(s);    
    10.     }    
    11. }    


    我们将java工程下/bin目录中的edu/hxraid/Resource.class和资源文件resource/res.txt一并打包进ResourceJar.jar中,不管jar包在系统的任何目录下,调用jar包中的Resource类都可以获得jar包中的res.txt资源,再也不会找不到res.txt文件了。

    后话:

    当然现在有jar包可以直接用于解决这个问题。

    当然这种工作,前人也早已经研究过了。XWork中有个工具类,叫做ClassLoaderUtil,可以深入读取jar包中的资源文件。Struts2就是用这个工具类读取所有的plugin中的struts-default.xml的。例如用户可以下载xwork-core-2.1.4-jdk14.jar,里面包括有一个类ClassLoaderUtil.java,其中的静态函数

    getResourceAsStream(String resourceName,Class callingClass)也可以解决这个问题。

    ClassLoaderUtil.getResourceAsStream("wei.txt",String.class);

  • 相关阅读:
    hdu 2019 数列有序!
    hdu 2023 求平均成绩
    HDU 5805 NanoApe Loves Sequence (思维题) BestCoder Round #86 1002
    51nod 1264 线段相交
    Gym 100801A Alex Origami Squares (求正方形边长)
    HDU 5512 Pagodas (gcd)
    HDU 5510 Bazinga (字符串匹配)
    UVALive 7269 Snake Carpet (构造)
    UVALive 7270 Osu! Master (阅读理解题)
    UVALive 7267 Mysterious Antiques in Sackler Museum (判断长方形)
  • 原文地址:https://www.cnblogs.com/zlingh/p/4237852.html
Copyright © 2011-2022 走看看