一、概述
在项目开发中,我们经常要去读取某一个配置文件中的内容,但是文件的路径获取的问题可能困扰了许多人,今天我们就来聊聊,在Java不同工程结构中如何获取文件的URL。先看以下代码:
public class Day02App {
public static void main(String[] args) {
Class<Day02App> clazz = Day02App.class;
System.out.println(clazz.getResource("test.xml"));
System.out.println(clazz.getResource("/test.xml"));
System.out.println(clazz.getClassLoader().getResource("test.xml"));
System.out.println(clazz.getClassLoader().getResource("/test.xml"));
}
}
项目结构如图所示:
输出结果:
下面我们对这些代码进行逐个分析:
关于Class类的getResource()方法源码如下:
public java.net.URL getResource(String name) {
//该方法的解释如下
name = resolveName(name);
//获取加载当前类的ClassLoader
ClassLoader cl = getClassLoader0();
//如果当前类的ClassLoader为null,就调用ClassLoader.getSystemResource()方法
if (cl==null) {
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();
}
//获取Day02App的全类名,day02.Day02App
String baseName = c.getName();
int index = baseName.lastIndexOf('.');
//将.替换成/,最后得到的文件名以day02/test.xml
if (index != -1) {
name = baseName.substring(0, index).replace('.', '/')
+"/"+name;
}
} else {
//如果文件名是以/开头,那么直接将/截取掉即可。得到的文件名为test.xml
name = name.substring(1);
}
return name;
}
类加载路径:Java 类加载路径告诉 java 解释器和 javac 编译器去哪里找它们要执行或导入的类。
从上面分析可知,这两行代码除了得到的文件名不同以外,方法调用是一样的。
System.out.println(clazz.getResource("test.xml"));
System.out.println(clazz.getResource("/test.xml"));
我们从分析中得到如果当前类加载器不为空的情况下,就调用getResource()方法,否则调用ClassLoader的静态方法getSystemResource()方法。这两个方法源码如下:
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);
}
//该方法就是获取系统类加载器,然后在调用getResource()方法而已
public static URL getSystemResource(String name) {
ClassLoader system = getSystemClassLoader();
if (system == null) {
return getBootstrapResource(name);
}
return system.getResource(name);
}
我们从上述代码可以看出,getSystemResource()也是调用getResource()方法,只是对应的类加载器实例不同而已。一个是系统类加载器,一个是Day02App类使用的类加载器(注意不要弄混淆二者的关系,因为类加载器可以自定义)。针对如下代码我们可以得出结论:
System.out.println(clazz.getResource("test.xml"));
System.out.println(clazz.getResource("/test.xml"));
System.out.println(clazz.getClassLoader().getResource("test.xml"));
System.out.println(clazz.getClassLoader().getResource("/test.xml"));
一二行代码是将文件名进行处理后在调用,ClassLoader类的getResource()方法。经过处理后,test.xml文件名变为day02/test.xml。/test.xml,变为test.xml。这就是ClassLoader用以搜索的资源名。
在绝大多数 Java 应用都会用到如下 3 中系统提供的类加载器:
- 启动类加载器(Bootstrap/Primordial/NULL ClassLoader):顶层的类加载器,没有父类加载器。负责加载 /lib 目录下的,或则被 -Xbootclasspath 参数所指定路径中的,并被 JVM 识别的(仅按文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录也不会被加载)类库加载到虚拟机内存中。所有被
- Bootstrap classloader 加载的类,它的 Class.getClassLoader 方法返回的都是 null,所以也称作 NULL ClassLoader。
扩展类加载器(Extension CLassLoader):由 sun.misc.Launcher$ExtClassLoader 实现,负责加载 <JAVA_HOME>/lib/ext 目录下,或被 java.ext.dirs 系统变量所指定的目录下的所有类库; - 应用程序类加载器(Application/System ClassLoader):由 sun.misc.Launcher$AppClassLoader 实现。它是 ClassLoader.getSystemClassLoader() 方法的默认返回值,所以也称为系统类加载器(System ClassLoader)。它负责加载 classpath 下所指定的类库,如果应用程序没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
一般来说,我们使用到的都是应用程序类加载器,我们在启动时会指定类加载的路径,在eclipse中是使用的Java代码编译后的路径,我们使用java命令时,可以通过-cp选项去指定,在上述项目结构中,我们查看eclipse编译后文件的输出路径如图所示:
也就是说,在eclipse中,程序运行之后,会从classes文件夹下根据文件名开始搜索资源。该目录结构如下:
所以这行代码输出为null。因为它的文件名为:day02/test.xml,我们并没有在day02文件夹下放置test.xml文件
System.out.println(clazz.getResource("test.xml"));
同理,这两行代码就能够找到资源:
System.out.println(clazz.getResource("/test.xml"));
System.out.println(clazz.getClassLoader().getResource("test.xml"));
这行代码不能,因为它的资源名为/test.xml。
System.out.println(clazz.getClassLoader().getResource("/test.xml"));
这是maven的项目结构,那么普通Java工程是怎么样的呢?仍然以上述代码为例,在普通的java工程中运行,项目结构、运行结果以及eclipse编译后的文件结构分别如下:
eclipse查看编译后的文件的输出路径方法如下:
选中工程->点击右键->Build Path->Configure Build Path
本文若有不足之处,请大家多多指正。谢谢大家。