1. ClassPath到底在哪儿?
之前一直有个误解,认为classpath是编译前概念,所以看到src文件夹下有main,有test,有resources等子目录,到底哪个是classpath就很懵。后来发现,其实classpath是.java文件被编译成.class字节码文件后,运行时的一个概念。
打开IDEA中的Project Structure
结构的右边列出了4个项目中的文件类型:
- Source Folders:表示的都是代码源文件目录,生成的class文件会输出到target->classess文件夹中,但是里面的源文件不会复制到target->classes文件夹中
- Test Source Folders: 表示的都是测试代码源文件目录,生成的class文件同样会输出到target->classess文件夹中,并且里面的源文件不会复制到target->classes文件夹中
- Resource Folders: 表示的都是资源文件目录,这些目录里面的文件会在代码编译运行被直接复制到target->classess文件夹中
- Excluded Folders:表示的是target文件夹生成的位置,target是IDEA编译后的一些class信息存放地,里面有子目录target->classes来存储编译后的字节码。
除了Excluded Folders,其他三个文件夹(src\main\java src\test\java src\main\resources)都会在编译后映射到classpath
2. this.getClass().getResource()还是this.getClass().getClassLoaderI().getResource()?
在加载资源时候,上述两个指令是我们经常用到的,这两个函数都是帮我们把相对路径转换成绝对路径,保证资源的加载与运行环境的独立,但是二者的使用上还是会有些细微的差别,让我们直接看源码
// Class类中的getResource方法
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);
}
Class类中的getResource方法会先对传入的String参数进行一定的处理,然后得到该类的类加载器,最后调用该类的类加载器的getResource()方法,所以Class类中的getResource方法是ClassLoader类中getResource方法的一种封装,差别就是在resolveName这个函数上。
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();
// 得到最后一个.的index
int index = baseName.lastIndexOf('.');
if (index != -1) {
// 将.替换为/ 得到类所在的文件夹名,然后与name进行拼接
name = baseName.substring(0, index).replace('.', '/')
+"/"+name;
}
} else {
name = name.substring(1);
}
return name;
}
如果name是“/”开头的,那么将其省略,然后返回这个字符串;如果name不是以"/“开头,那么先得到该类所在的文件夹名,然后与name进行拼接,返回这个字符串。举例,如果我们传入的是”"(空字符串),那么经过resolveName处理,返回的就是该类所在包名(例如 io.github.ericwu0930);如果传入的是"/",那么经过resolveName处理,返回的是""(空字符串)
结合类加载器中的相关知识,对于Application ClassLoader这一类加载器,因为Application ClassLoader是从classpath中寻找所需要加载的类,换句话说,classpath是根目录。因此,如果传入的字符串以"/“开头,那么就是从classpath目录下寻找资源,如果传入的字符串不以"“开头,那么就是从该类所在包下寻找资源。
通过一个实验可以更清楚些。
package io.github.ericwu0930;
/**
* @author erichwu
* @date 2020/6/6
*/
public class Test {
public void getResource(){
System.out.println(this.getClass().getResource("/")); // 情况1
System.out.println(this.getClass().getResource("")); // 情况2
System.out.println(this.getClass().getClassLoader().getResource("/")); // 情况3
System.out.println(this.getClass().getClassLoader().getResource("")); // 情况4
}
public static void main(String[] args) {
Test test=new Test();
test.getResource();
}
}
file:/Users/eric/javaProjects/getResourceTest/out/production/getResourceTest/
file:/Users/eric/javaProjects/getResourceTest/out/production/getResourceTest/io/github/ericwu0930/
null
file:/Users/eric/javaProjects/getResourceTest/out/production/getResourceTest/
经过我们上面的分析,情况1和情况4是等价的,答案也是如此;情况3这种调用方式是错误的。