JAVA 资源获取方法

      在开发java程序的过程中,经常要做的一件事就是获取资源。在java程序中资源的存放位置各异,有存放在classpath中,有存放在文件系统中,也有存放在web应用中的。根据资源位置的不同,java程序提供不同的获取资源的方法。

URL url = this.getClass().getResource("resource_name");
URL url = this.getClass().getClassLoader().getResource("resource_name");
URL url = Thread.currentThread().getContextClassLoader().getResource("resource_name");

      第一行代码中是利用Class类的实例来获取,第二行代码是使用加载当前类的classloader来获取。通过查看源代码会发现class类的实例最后还是委托加载他的classloader来获取资源。

  public java.net.URL getResource(String name) {
    name = resolveName(name);
    ClassLoader cl = getClassLoader0();
    if (cl==null) {
    // A system class.
    return ClassLoader.getSystemResource(name);
    }
    return cl.getResource(name);
    }

      从上面的代码中可以看出,对于资源的加载并没有像类加载所采用的双亲委托机制。而是当前类的classloader不为null的情况下先从当前类的classloader中加载资源,只有当前类的classloader为null的时候才从system classloader中去加载资源。这样可以方便自定义配置类覆盖一些默认配置。当然,j2se应用中如果没有特别定制classloader时,自己写的类都是被system classloader加载的。那么利用class去获取资源和利用classloader去获取资源有什么区别呢?其区别就在 resolveName(name)这个方法中。两种方式对于资源名称的表示方式不同。下面是一个简单的包结构,/表示类路径(classpath)的根

   /
     |-com.cn.test
      |-Resource.class
      |-properties1.txt
     |-properties2.txt
    // 获取与当前类在同一个包下的资源
    URL url1 = this.getClass().getResource("properties1.txt");
    // 获取com.cn.test包下的资源,需加/(以此表明是绝对路径,根路径是classpath)
    URL url2 = this.getClass().getResource("/com/cn/test/properties1.txt");
    // 获取类路径根下的资源
    URL url3 = this.getClass().getClassLoader().getResource("properties2.txt");
    // 获取包com.cn.test包下的资源(默认根路径就是classpath,是相对classpath的相对路径)
    URL url4 = this.getClass().getResource("com/cn/test/properties1.txt");

B、获取文件系统中的资源

   // 1、获得File对象
    File file = new File("properties.txt");
    // 2、获得File对象的字节流
    InputStream in = new FileInputStream(file);

      值得注意的是在File的构造函数File(String name) 中的name参数可以是相对路径和绝对路径。相对路径是相对于System.getProperties(“user.dir”)的。其中user.dir 是user work directory,也即是程序命令执行的当前路径,具有不确定性。

C、获取web应用中的资源

 servletContext.getResourceAsStream(resource_name);

resource_names为相对于webroot的路径表示。例如获取web.xml,resource_name表示为”/WEB-INF/web.xml”

      对于以上的资源获取方式同可以通过java.net.URL实现。

      从名称上来看 URL(Uniform Resource Locator) 统一资源定位器。看起来很好很强大。但很多时候使用它并不能定位到我们需要的资源。

      首先,jdk的URL能访问的协议非常有限,常用的有http,file,ftp等等。并没有提供对classpath和servletContext中的资源的获取方法。

       另外,它没有提供判断资源是否存在的方法。每次只有等真正去获取资源的时候抛出异常才能知道资源无法获取。

      其次,URL这个类的职责未划分清楚,既用来表示资源有用来获取其资源。

从jar包中读取资源文件

      应用程序常常在代码中读取一些资源文件(比如图片,音乐,文本等等)。在单独运行的时候这些简单的处理当然不会有问题。但是,如果我们把代码打成一个jar包以后,即使将资源文件一并打包,这些东西也找不出来了。看看下面的代码:

package com.kuzury;
import java.io.*;
public class Resource {
    public  void getResource() throws IOException{
        File file=new File("bin/resource/resource.txt");
        BufferedReader br=new BufferedReader(new FileReader(file));
        String s="";
        while((s=br.readLine())!=null)
            System.out.println(s);
    }
}   

这段代码写在Eclipse建立的java Project中,其目录为:(其中将资源文件resource.txt放在了bin目录下,以便打成jar包)

   1、src/
         src/com/kuzury/Resource.java
  2、bin/
         bin/resource/resource.txt
         bin/com/kuzury/Resource.class

       很显然运行源代码是能够找到资源文件resource.txt。但当我们把整个工程打成jar包以后(ResourceJar.jar),这个jar包内的目录为:
com/kuzury/Resource.class
resource/resource.txt

       而这时jar包中Resource.class字节 码:String "bin/resource/resource.txt"将无法定位到jar包中的resource.txt位置上。就算把bin/目录去掉:String "resource/resource.txt"仍然无法定位到jar包中resource.txt上。

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

      那么把资源打入jar包,无论ResourceJar.jar在系统的什么路径下,jar包中的字节码程序都可以找到该包中的资源。这会是幻想吗?

      当然不是,我们可以用类装载器(ClassLoader)来做到这一点:

       (1) ClassLoader 是类加载器的抽象类。它可以在运行时动态的获取加载类的运行信息。 可以这样说,当我们调用ResourceJar.jar中的Resource类时,JVM加载进Resource类,并记录下Resource运行时信息 (包括Resource所在jar包的路径信息)。而ClassLoader类中的方法可以帮助我们动态的获取这些信息:

   public URL getResource(String name)

      查找具有给定名称的资源。资源是可以通过类代码以与代码基无关的方式访问的一些数据(图像、声音、文本等)。并返回资源的URL对象。

  public InputStream getResourceAsStream(String name);

返回读取指定资源的输入流。这个方法很重要,可以直接获得jar包中文件的内容。

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

import java.io.*;
import java.net.URL;
public class Resource {
    public  void getResource() throws IOException{   
              //查找指定资源的URL,其中resource.txt仍然开始的bin目录下
        URL fileURL=this.getClass().getResource("/resource/resource.txt"); //(注意这里以反斜杠/开头,表示是绝对路径,没有反斜杠/为相对路径)      
          System.out.println(fileURL.getFile());
    }
    public static void main(String[] args) throws IOException {
        Resource res=new Resource();
        res.getResource();
    }
}

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

import java.io.IOException;
import com.kuzury.Resource;
public class TEST {
    public static void main(String[] args) throws IOException {
        Resource res=new Resource();
        res.getResource();
    }
}

这时的运行结果是:file:/C:/ResourceJar.jar!/resource/resource.txt (fileURL.getFile()的结果)

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

      File f=new File("C:/ResourceJar.jar!/resource/resource.txt");

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

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

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

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

使用classloader的getResource方法获得执行jar的运行路径

在Java处理的文件系统中,目录的表示方式有两种:

(1)绝对目录,它以”/”为起始字符,代表从根目录下开始寻找给出的目录,如/c:/java

(2)相对路径,它以不带“/”的目录名表示,表示以当前Java程序正在运行的目录作为起始目录来寻找给出的目录。如java /classes。在相对路径中,有一些特定的字符,可以代表特的的目录,比如,“.”代表当前目录,“..”代表当前目录的上一级目录。在网上很多给出 的例子中,就是利用”.”作为目录名,构造File对象的实例,然后通过File对象的方法来获取当前程序运行的目录。

这种方法虽然简单,但有时不能正确的得出当前程序的运行目录。原因在于,运行Java程序不一定要进入到该程序的类文件或JAR文件所在的目录,只要在运行时指定了正确的类路径信息,就可以在任何目录中运行Java程序,此时利用这种方法只能得到发出运行命令时所在的目录信息。

从上面的分析可以看出,对于很多Java程序,尤其是WEB程序,利用当前路径的“.”表示法,都不能满足要求。

利用java.lang.Class的getClassLoader方法,可以获得给定类的ClassLoader实例,它的getResource方法 可以获得当前类装载器中的资源的位置,我们可以利用类文件的名称作为要查找的资源,经过处理后就可获得当前Java程序的运行位置信息,其伪代码如下:

获得Class参数的所在的类名
取得该类所在的包名
将包名转换为路径
利用getResource得到当前的类文件所在URL
利用URL解析出当前Java程序所在的路径

 public static String getAppPath(Class cls) {
        //检查用户传入的参数是否为空
        if (cls == null)
            throw new java.lang.IllegalArgumentException("参数不能为空!");
        ClassLoader loader = cls.getClassLoader();
        //获得类的全名,包括包名
        String clsName = cls.getName() + ".class";
        //获得传入参数所在的包
        Package pack = cls.getPackage();
        String path = "";
        //如果不是匿名包,将包名转化为路径
        if (pack != null) {
            String packName = pack.getName();
            //此处简单判定是否是Java基础类库,防止用户传入JDK内置的类库
            if (packName.startsWith("java.") || packName.startsWith("javax."))
                throw new java.lang.IllegalArgumentException("不要传送系统类!");
            //在类的名称中,去掉包名的部分,获得类的文件名
            clsName = clsName.substring(packName.length() + 1);
            //判定包名是否是简单包名,如果是,则直接将包名转换为路径,
            if (packName.indexOf(".") < 0)
                path = packName + "/";
            else {//否则按照包名的组成部分,将包名转换为路径
                int start = 0, end = 0;
                end = packName.indexOf(".");
                while (end != -1) {
                    path = path + packName.substring(start, end) + "/";
                    start = end + 1;
                    end = packName.indexOf(".", start);
                }
                path = path + packName.substring(start) + "/";
            }
        }

        //调用ClassLoader的getResource方法,传入包含路径信息的类文件名
        java.net.URL url = loader.getResource(path + clsName);
        //从URL对象中获取路径信息

        String realPath = url.getPath();
        //去掉路径信息中的协议名"file:"
        int pos = realPath.indexOf("file:");
        if (pos > -1)
            realPath = realPath.substring(pos + 5);


        //去掉路径信息最后包含类文件信息的部分,得到类所在的路径
        pos = realPath.indexOf(path + clsName);
        realPath = realPath.substring(0, pos - 1);

        //如果类文件被打包到JAR等文件中时,去掉对应的JAR等打包文件名
        if (realPath.endsWith("!"))
            realPath = realPath.substring(0, realPath.lastIndexOf("/"));


        //结果字符串可能因平台默认编码不同而不同。因此,改用 decode(String,String) 方法指定编码。
        try {
            realPath = java.net.URLDecoder.decode(realPath, "utf-8");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        System.out.println(realPath);
        return realPath;
    }//getAppPath定义结束
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值