ClassLoader.getResource()原理

一、概述

在项目开发中,我们经常要去读取某一个配置文件中的内容,但是文件的路径获取的问题可能困扰了许多人,今天我们就来聊聊,在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 中系统提供的类加载器:

  1. 启动类加载器(Bootstrap/Primordial/NULL ClassLoader):顶层的类加载器,没有父类加载器。负责加载 /lib 目录下的,或则被 -Xbootclasspath 参数所指定路径中的,并被 JVM 识别的(仅按文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录也不会被加载)类库加载到虚拟机内存中。所有被
  2. Bootstrap classloader 加载的类,它的 Class.getClassLoader 方法返回的都是 null,所以也称作 NULL ClassLoader。
    扩展类加载器(Extension CLassLoader):由 sun.misc.Launcher$ExtClassLoader 实现,负责加载 <JAVA_HOME>/lib/ext 目录下,或被 java.ext.dirs 系统变量所指定的目录下的所有类库;
  3. 应用程序类加载器(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
在这里插入图片描述
本文若有不足之处,请大家多多指正。谢谢大家。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值