2023-08-18 重新修改,对 ClassLoader 重新理解 此处参考 编译后,在.java同路径目录下生成class文件。 默认java虚拟机要从classpath环境变量的路径中搜索class文件去执行,对于java虚拟机来说,这不是类文件,而是类。它只有类路径,而没有文件系统路径。而classpath环境变量正是为java虚拟机提供搜索类路径的环境。注意,虚拟机不会递归搜索classpath定义的路径。 也就是说,上面的java文件可以正确编译,但却不能执行。但如果将classpath设置为 于是上面的HelloWorld.java编译后,可以直接执行: 对于 但是,如果使用 Class 类 的 今天在Java程序中读取resources资源下的文件,由于对Java结构了解不透彻,遇到很多坑。正常在Java工程中读取某路径下的文件时,可以采用绝对路径和相对路径,绝对路径没什么好说的,相对路径,即相对于当前类的路径。在本地工程和服务器中读取文件的方式有所不同,以下图配置文件为例: Java类中需要读取properties中的配置文件,可以采用文件(File)方式进行读取: 注意:当在IDEA中运行(不部署在服务器上),可以读取到该文件; 当工程部署到Tomcat中时,按照上边方式,则会抛出异常:FileNotFoundException。原因:Java工程打包部署到Tomcat中时,properties的路径变到顶层(classes下),这是由Maven工程结构决定的。由Maven构建的web工程,主代码放在src/main/java路径下,资源放在src/main/resources路径下,当构建jar包 或 war包时,JVM虚拟机会自动编译java文件为class文件存放在 target/classes目录下,resource资源下的文件会原封不动的拷贝一份到 target/classes 目录下: 此时读取资源文件时,采用流(Stream)的方式读取,并通过JDK中Properties类加载,可以方便的获取到配置文件中的信息: 在Java中获取资源的时候,经常用到getResource和getResourceAsStream path不以'/'开头时,默认是从此类所在的包下取资源; 运行结果为: path不能以'/'开头,path是指类加载器的加载范围,在资源加载的过程中,使用的逐级向上委托的形式加载的,'/'表示Boot ClassLoader,类加载器中的加载范围,因为这个类加载器是C++实现的,所以加载范围为null。如下所示: 运行结果为: 从上面可以看出: 从上面就可以看才出来:Class.getResource和ClassLoader.getResource本质上是一样的。至于为什么Class.getResource(String path)中path可以'/'开头,是因为在name = resolveName(name);进行了处理:
文件系统路径 与 类路径(classpath)
java编译器编译.java文件和java虚拟机执行.class文件时的路径和写法不一样。在没有设置任何classpath环境变量的情况下,javac可以编译全路径的.java文件。例如:javac d:\myjava\HelloWorld.java
".;d:\myjava\",则java虚拟机将先从当前路径搜索,再从d:\myjava下搜索class文件。java HelloWorld
小结
ClassLoader来说,类路径,而没有文件系统路径。类路径都是基于 classpath ,没有相对路径和绝对路径之分,所以不需要在资源路径前加上斜杠 / ,如使用的是绝对路径(例如 /myresource.txt),那么系统会尝试从文件系统的绝对路径中查找资源,而不是从类路径中查找,这可能导致找不到资源文件。例如InputStream inputStream = getClass().getClassLoader().getResourceAsStream("myresource.txt");
getResourceAsStream() ,是有相对路径和绝对路径,查看源代码,会发现其也是使用 ClassLoader 进行资源加载,但是会对 传入的资源名字进行处理,如果资源参数没有以 / 开头,就在前面拼接对应class的package 名字,然后再交给 ClassLoader 进行资源加载,给人产生一种相对路径的感觉 private String resolveName(String name) {
if (!name.startsWith("/")) {
Class<?> c = this;
while (c.isArray()) {
c = c.getComponentType();
}
String baseName = c.getPackageName(); // 获取package名字
if (baseName != null && !baseName.isEmpty()) {
name = baseName.replace('.', '/') + "/" + name; // 拼接package名字,在返回
}
} else {
name = name.substring(1);
}
return name;
}
例子

1、本地读取资源文件
File file = new File("src/main/resources/properties/test.properties");
InputStream in = new FileInputStream(file);
2、服务器(Tomcat)读取资源文件

InputStream in = this.getClass().getResourceAsStream("/properties/test.properties");
Properties properties = new Properties();
properties.load(in);
properties.getProperty("name");
3、读取方式比较
3.1、Class.getResource(String path)
path以'/'开头时,则是从项目的ClassPath根下获取资源。在这里'/'表示ClassPath的根目录。
JDK设置这样的规则,是很好理解的,path不以'/'开头时,我们就能获取与当前类所在的路径相同的资源文件,而以'/'开头时可以获取ClassPath根下任意路径的资源。
如下所示的例子:public class Test
{
public static void main(String[] args)
{
System.out.println(Test.class.getResource(""));
System.out.println(Test.class.getResource("/"));
}
}
file:/D:/work_space/java/bin/net/swiftlet/
file:/D:/work_space/java/bin/
3.2、Class.getClassLoader().getResource(String path)
public class Test
{
public static void main(String[] args)
{
System.out.println(Test.class.getClassLoader().getResource(""));
System.out.println(Test.class.getClassLoader().getResource("/"));
}
}
file:/D:/work_space/java/bin/
null
class.getResource("/") == class.getClassLoader().getResource("")
其实,Class.getResource和ClassLoader.getResource本质上是一样的,都是使用ClassLoader.getResource加载资源的。下面请看一下jdk的Class源码: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);
}
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;
}