Java getResource 讲解

在 Java 中访问资源我们一般使用 getResource() 方法,亦或者直接new File()然后传入一个文件路径获取资源文件。但是这两者究竟有什么区别呢?由于平常在使用的时候经常会傻傻分不清楚,因此这里写篇博客整理下我的笔记。

Java 运行中资源的说明

这里我们将运行中 Java 进程可以获取到的资源简单分为两类:

存在于文件系统中的资源

这类资源一般直接存在与磁盘中,我们可以直接从资源管理器中访问到。当我们通过应用访问这类资源时既可以使用 File对象通过文件系统获取到,也可以使用 getResource()方法获取到(仅限于classpath中)。
在这里插入图片描述

注意 getResource() 方法的获取范围仅限于 指定的范围classpathclasspath 之外的资源是无法获取到的。
在这里插入图片描述

存在于jar包中的资源

这类资源如字面含义,存在于 jar 包中,而 jar 是被我们包含在 classpath 中的。所以这一类资源妥妥的可以被我们称为代码中的资源。由于这种资源是包含在一个文件中,我们用File只能获取到jar包,而无法获取到 jar包中的资源。
在这里插入图片描述
这类资源一般是存在于 classpath 中的文件,我们可以使用上面的 getResource()方法获取到其路径,(通常样式是资源所在文件的路径+资源在文件中的路径)但是没有办法直接通过文件读取到。但是可以通过 classLoader 对象中的 getResourceAsStream()方法获取到资源的输入流。
在这里插入图片描述

通过 getResourceAsStream() 方法获取资源的输入流实际上是通过 URL 对象,通过 openStream()方法打开的,因此我们只需要拿到正确的资源URL 地址,就能够获得该类资源的输入流了。

在这里插入图片描述

Java 中 getResource() 说明

在 Java 中类对象如java.lang.String.class 或者 具体的类加载器对象如 java.lang.String.class.getClassLoader()中都有getResource()方法,但是在类对象上调用getResource()方法时,路径会和使用类加载器对象上调用getResource()方法有所区别。

getResource()方法中传入的路径为绝对路径时,两者都是从 classpath 开始寻找资源:

Main.class.getResource("/com/ghimi/demo/data.json");
Main.class.getClassLoader().getResource("/com/ghimi/demo/data.json");
// 两者实际获取到的资源相同

getResource()方法中传入的路径为相对路径时,两者查找资源的方式会有所差异

// 当使用类对象加载资源时,会从类对象目录下去寻找该资源
// 如 Main.class 位于 demo01.jar 包的 /com/ghimi/demo 目录下
// getResource 方法会尝试从该目录下查找 data.json 资源
Main.class.getResource("data.json");
// 当使用类加载器对象加载资源时,会从 classpath 根目录下查找该资源
// getResource 方法会尝试从 classpath 根目录下找 data.json 资源
Main.class.getClassLoader().getResource("data.json");

为什么会导致这样的差异呢,现在我们打开源码,去分析一下这 class 对象上的 getResource() 方法与 classLoader 对象上的 getResource()方法在实现上的区别。

class 对象的 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);
}

方法开始,调用了一个 resolveName(name) 方法对入参 name 做了一下处理,而 resolveName(name) 方法是这样实现的:

private String resolveName(String name) {
    if (name == null) {
    	// 当入参为 null 时 返回 null
        return name;
    }

    if (!name.startsWith("/")) {
    	// 当 name 不以 "/" 开头时,表示这是一个相对路径,则进行如下处理
        Class<?> c = this;
        // 判断当前 class 对象是否是一个数组对象
        while (c.isArray()) {
        	// 如果当前 class 对象是一个数组对象,则取数组内 class 类型
            c = c.getComponentType();
        }
        // 获取当前 class 对象的全限定类名如 java.lang.String
        String baseName = c.getName();
        int index = baseName.lastIndexOf('.');
        if (index != -1) {
            // 取当前 class 对象的包名如 java.lang
            // 将 . 替换为 "/" 路径名
            name = baseName.substring(0, index).replace('.', '/')
                +"/"+name;
        }
    } else {
        // 当name以 "/" 开头时,表示这是一个绝对路径,则返回去掉 "/" 的 name
        name = name.substring(1);
    }
    return name;
}

也就是说,我们在调用 class 对象的 getResource() 方法时,会首先对入参进行处理。且处理方式与两种因素存在关系:

  • 输入参数是否是一个绝对路径
  • 当前class对象的包路径

而在处理入参时,处理逻辑如下:

java.lang.String.class
全限定类名`java.lang.String`
包`java.lang`
路径形式`java/lang`

可以看到,类对象上的getResource() 方法在对资源路径处理时,如果是相对路径或者直接的资源名,就会根据当前类对象的包路径进行处理,也即会认为资源与代码在相同路径下

总结:class 对象在获取资源时,如果使用相对路径,则认为资源与 class对象处于相同路径

也就是资源位置与代码位置相关,当代码处于磁盘上时,会认为资源也位于磁盘上;
在这里插入图片描述
使用 getResource() 方法获取到的 URL

file:/D:/Desktop/code/demo09/com/ghimi/demo/data.json

当代码处于jar包中时,资源也会被认为处于 jar包中。
在这里插入图片描述
使用 getResource() 方法获取到的 URL

jar:file:/D:/Desktop/code/demo09/demo09.jar!/com/ghimi/demo/data.json

实际上 class对象的 getResource()方法最终调用的方法也是 classLoader对象中的 getResource()方法。下面我们来看下 classLoader 对象中 getResource() 方法

classLoader对象上 getResource() 方法源码

classLoader 对象可以从每个类对象获取到,如下:

Main.class.getClassLoader().getResource("data.json")

getResource()源码:

public URL getResource(String name) {
    URL url;
    // 双亲委派模型,递归向上请求尝试获取资源
    if (parent != null) {
        url = parent.getResource(name);
    } else {
    	// 尝试中启动类加载器获取资源
        url = getBootstrapResource(name);
    }
    // 使用当前的 findResource() 获取资源
    if (url == null) {
        url = findResource(name);
    }
    return url;
}

可以看到,相交于 class 对象的 classLoader 对象上的 getResource()方法缺少了 resolveName()方法,这意味着传入的参数不论是绝对路径还是相对路径,都是以 classpath 为根路径进行查找的。

// 返回 null 因为 data.json 位于 /com/ghimi/demo 下
// 从 classloader getResource 时,会从 classpath 中查找该资源故而查找失败
Main.class.getClassLoader().getResource("data.json");
----------------------------------------
// 返回对应的 url ,因为会拼接 com.ghimi.demo.Main 中的 /com/ghimi/demo到 data.json上
// 长 class getResource时,会从 classpath 中查找 /com/ghimi/demo/data.json 而查找成功
Main.class.getResource("data.json");

getResource()方法获取不到资源时,会返回 null.

参考资料

彻底搞懂Class.getResource和ClassLoader.getResource的区别

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值