J2SE中资源的加载,隶属IO的范畴,这是一个老生常谈的问题。
1. 先看一个相关测试的小demo,其资源和项目结构如下图。
2. 测试源码及注释如下,这一部分请集合下文分析来看。
package cn.wxy.res.test;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import org.junit.Before;
import org.junit.Test;
/**
* @Title ResLoader.java
* @Package cn.wxy.res.test
* @Description
* @author wxy51
* @date 2016年8月2日 下午4:56:59
* @version V1.0
*/
public class ResLoader {
/**
* 打印JVM启动时设置的CLASSPATH值
*/
@Before
public void setUp() {
String classpath = (String) System.getProperties().get("java.class.path");
// 输出结果:D:\dev_code\workspace_luna3\res\bin;
System.out.println(classpath);
}
/**
* Class相对路径资源加载测试
*/
@Test
public void testClass() {
// ResLoader.class的package是cn.wxy.res.test
// 对于a文件而言,其最终的资源名是package+name
// 对于b文件而言,如果资源加载目录不往上跳,则最终b资源名为cn/wxy/res/test/b.propertes
// 所以为了得到正确的资源名,应该把自动添加的ResLoader.class的package名去掉
// cn/wxy/res/test/../../../../b.propertes
InputStream aIS = ResLoader.class.getResourceAsStream("a.properties");
InputStream bIS = ResLoader.class.getResourceAsStream("../../../../b.properties");
printResult(aIS, bIS);
}
/**
* Class资源加载绝对路径测试
*/
@Test
public void testClassAbsolutePath(){
InputStream aIS = ResLoader.class.getResourceAsStream("/cn/wxy/res/test/a.properties");
InputStream bIS = ResLoader.class.getResourceAsStream("/b.properties");
printResult(aIS, bIS);
}
/**
* ClassLoader资源加载相对路径测试
*/
@Test
public void testClassLoader() {
// 对于这一部分的内容,参看setUp中System.getProperties().get("java.class.path");
// JVM启动的时候,会设置一些系统环境变量,其中CLASSPATH可以通过“java.lang.path”获取到
// 对于本例中ClassLoader的工作目录即是java.class.path的value
// java.class.path = D:\dev_code\workspace_luna3\res\bin;
// 因此对于a文件需要补充包名,对于b文件直接写即可
InputStream aIS = ResLoader.class.getClassLoader().getResourceAsStream("cn/wxy/res/test/a.properties");
InputStream bIS = ResLoader.class.getClassLoader().getResourceAsStream("b.properties");
printResult(aIS, bIS);
}
private void printResult(InputStream aIS, InputStream bIS) {
Properties p = new Properties();
// 测试a
try {
p.load(aIS);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(p.get("res")); // 输出a_file
// 测试b
try {
p.load(bIS);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(p.get("res")); // 输出b_file
}
}
一、java.lang.Class的getResourceAsStream
其实java.lang.Class.getResourceAsStream(String name)方法加载资源的实现就是依靠java.lang.ClassLoader,只不过是稍微包装了一下(使得该方法支持绝对路径加载资源,即以“/”开头,因为ClassLoader只支持相对路径而不支持绝对路径),主要关注的是resolveName方法。
这一部分的逻辑大概两个步骤:
1. 如果入参name不是以"/"开头(即相对路径),那么在resolveName的时候,把Class的包名取出来加上入参name作为资源名,然后通过类加载器加载资源;
2. 如果入参name是以"/"开头(即绝对路径),那么在resolveName的时候去掉开头的"/"作为资源名,然后通过类加载器加载资源;
这两种方法,最终得到的都是相对路径的资源名(即不以“/”开头),因为其底层实现ClassLoader不支持绝对路径。
二、java.lang.ClassLoader的getResourceAsStream
这一部分在源码中并没有太多可以看的地方,主要关注点在JVM启动时会设置环境变量CLASSPATH,可以通过System.getProperties().get("java.class.path")方法来获取,一般情况下,这个值就是当前应用的bin目录,参见上文测试的小demo中setUp方法。
ClassLoader资源加载的原理也比较简单,最主要要关注的是该方式是不支持绝对路径的,因为ClassLoader会直接定位到CLASSPATH所在目录,然后在该目录的基础上进行资源的加载,因此该方式不支持绝对路径,即不支持以“/”开头。
三、Tomcat环境
四、未提及
后续抽时间把Tomcat中的资源加载捋一捋,主要是ServletContext的两个方法getRealPath和getResourceAsStream。
附注:
本文如有错漏,烦请不吝指正,谢谢!