Java 读取资源、配置文件

Class 与 ClassLoader

写 java 代码时常常需要加载一些外部的资源,通常我们会使用全路径名加载一份资源,比如:C:\Users\XXX\Desktop\abc.jpg
但是,有些时候我们需要加载的是源代码路径下的资源或者配置文件等等,更习惯于使用相对路径,或者直接给一个文件名,就希望能够找到我们需要的配置文件。
如何做到?

常见的方法是使用了 class.getResource 或 classloader.getResource
并且使用绝对路径是一个很差的方式,它会导致你的程序移植性很差, 尽量使用相对路径

这两个方法看起来很相似,他们直接有什么区别?
查看源代码:
Class.getResourceAsStream(String name)

public InputStream getResourceAsStream(String name) {

    name = resolveName(name);

    ClassLoader cl = getClassLoader0();

    if (cl==null) {

        // A system class.

        return ClassLoader.getSystemResourceAsStream(name);

    }

    return cl.getResourceAsStream(name);
}

上面的代码可以看出, Class.getResourceAsStream(String name) 最终还是调用了 classloader.getResourceAsStream(String name)
但是两者还是有一些区别的,注意 name =resolveName(name)这一行, Class.getResourceAsStream(String name)在这里做了一些处理:
Class.resolveName(String name)

private String resolveName(String name) {
    if (name == null) {

        return name;

    }

    if (!name.startsWith("/")) {

        Class c = this;

        while (c.isArray()) {

            c = c.getComponentType();

        }

        String baseName = c.getName();

        int index = baseName.lastIndexOf('.');

        if (index != -1) {

            name = baseName.substring(0, index).replace('.', '/')

                +"/"+name;

        }

    } else {

        name = name.substring(1);

    }

    return name;

}

现在看这段代码还有点云里雾绕,不妨写几行代码测试一下,看看这段代码到底在干嘛:

public class App {

    public static void main(String[] args) {

        System.out.println("App.class.getClassLoader().getResource(\"\") : " + App.class.getClassLoader().getResource(""));

        System.out.println("App.class.getClassLoader().getResource(\"/\") : " + App.class.getClassLoader().getResource("/"));

        System.out.println("App.class.getResource(\"\") : " + App.class.getResource(""));

        System.out.println("App.class.getResource(\"/\") : " + App.class.getResource("/"));

    }

}

输出

App.class.getClassLoader().getResource("") : file:/D:/workspace/eclipse/cluster/TestClassloader/bin/

App.class.getClassLoader().getResource("/") : null

App.class.getResource("") : file:/D:/workspace/eclipse/cluster/TestClassloader/bin/space/yukai/

App.class.getResource("/") : file:/D:/workspace/eclipse/cluster/TestClassloader/bin/

虽然上面的代码使用了 getResource,但与 getResourceAsStream 大同小异。

可以看到,

calssloder.getResource("")方法返回了 classpath 根路径(eclipse 工程中,编译生成的类文件存放在 /bin 目录下);

calssloder.getResource("/")方法返回 null,说明 calssloder.getResource 不支持以”/“开头的参数;

class.getResource("")方法返回了 App.class 所在的路径;

class.getResource("/")calssloder.getResource("")表现一致,返回了 classpath 的根路径

再回顾上面的代码,是否有一点明白了呢?

读取 jar 包所在的位置

有时候需要知道 jar 包所在的位置,比如我们的项目需要一个默认的日志文件输出路径,这个路径就可以是运行时 jar 包所在的目录。如何获取 jar 包所在的目录?

URL url = App.class.getProtectionDomain().getCodeSource().getLocation();

String path = url.toURL().getPath();

注意,上面的方法仅适用 jdk1.5 及以上

附实际使用方式

1.  package com.zkn.newlearn.others;  
2.    
3.  import java.io.IOException;  
4.  import java.io.InputStream;  
5.  import java.util.Properties;  
6.    
7.  import com.zkn.newlearn.gof.singleton.SimpleFactoryTest01;  
8.    
9.  /** 
10.   * 读取资源文件的五种方式 
11.   * @author zkn 
12.   */  
13.    
14.  public class ClassReadResourceDemo {  
15.    
16.      public static void main(String[] args) {  
17.          /** 
18.           * 第一种方式 用类加载器读取资源文件。 
19.           * 适用情形:资源文件和类文件在不在同一目录都可以。 
20.           * 注意:getResourceAsStream里的参数要 
21.           * 写资源文件的全限定路径,包名+文件名 
22.           * 开头千万不要写"/" 
23.           */  
24.          InputStream is = ClassReadResourceDemo.class.  
25.                  getClassLoader().getResourceAsStream("com/zkn/newlearn/io/config.properties");  
26.          Properties prop = new Properties();  
27.          try {  
28.              prop.load(is);  
29.              System.out.println(prop.getProperty("key"));  
30.              is.close();  
31.          } catch (IOException e) {  
32.              e.printStackTrace();  
33.          }  
34.          /** 
35.           * 第二种写法:用class.getResourceAsStream()(其实还是用的类加载器) 
36.           * 适用情形:如果资源文件和类文件在同一包下,直接写资源文件的名称就行了, 
37.           * 注意:资源文件的名称前面不需要加“/” 
38.           */  
39.          is = ClassReadResourceDemo.class.getResourceAsStream("config.properties");  
40.          try {  
41.              prop.load(is);  
42.              System.out.println(prop.getProperty("key"));  
43.              is.close();  
44.          } catch (IOException e) {  
45.              e.printStackTrace();  
46.          }  
47.          /** 
48.           * 第三种写法:用class.getResourceAsStream()(其实还是用的类加载器) 
49.           * 适用情形:这个写法适用的情形是资源文件和类文件不在同一个目录下的情况 
50.           * 注意:开头一定要加上”/“ 
51.           */  
52.          is = ClassReadResourceDemo.class.getResourceAsStream("/com/zkn/newlearn/io/config.properties");  
53.          try {  
54.              prop.load(is);  
55.              System.out.println(prop.getProperty("key"));  
56.              is.close();  
57.          } catch (IOException e) {  
58.              e.printStackTrace();  
59.          }  
60.          /** 
61.           * 第四种写法:用class.getResourceAsStream() 
62.           * 适用情形:这种写法适用于资源文件在根目录下的情况 
63.           * 注意:文件名称前面一定要加上”/“ 
64.           */  
65.          is = ClassReadResourceDemo.class.getResourceAsStream("/config.properties");  
66.          try {  
67.              prop.load(is);  
68.              System.out.println(prop.getProperty("key"));  
69.              is.close();  
70.          } catch (IOException e) {  
71.              e.printStackTrace();  
72.          }  
73.          /** 
74.           * 第五种写法:用类加载器来读取资源文件 
75.           * 适用情形:资源文件在跟目录下 
76.           * 注意:资源文件名称前面一定不要加”/“ 
77.           */  
78.          is = ClassReadResourceDemo.class.getClassLoader().getResourceAsStream("config.properties");  
79.          try {  
80.              prop.load(is);  
81.              System.out.println(prop.getProperty("key"));  
82.              is.close();  
83.          } catch (IOException e) {  
84.              e.printStackTrace();  
85.          }  
86.      }  
87.  }