jvm 类加载机制之getResource和getResourceAsStream 使用 和原理解析 (5)

Java中取资源时,经常用到Class.getResource和ClassLoader.getResource,这里来看看他们在取资源文件时候的路径问题。

1 :Class.getResource(String path)使用注意事项和示例

path不以’/'开头时,默认是从此类所在的包下取资源;
path  以’/'开头时,则是从ClassPath根下获取;

什么意思呢?看下面这段代码的输出结果就明白了:

package testpackage;
public class TestMain {
    public static void main(String[] args) {
        System.out.println(TestMain.class.getResource(""));
        System.out.println(TestMain.class.getResource("/"));
    }
}

输出结果:

file:/E:/workspace/Test/bin/testpackage/
file:/E:/workspace/Test/bin/

上面说到的【path以’/'开头时,则是从ClassPath根下获取;】在这里就是相当于bin目录(Eclipse环境下)。

再来一个实例,假设有如下Project结构:

如果我们想在TestMain.java中分别取到1~3.properties文件,该怎么写路径呢?代码如下:

package testpackage;

public class TestMain {

    public static void main(String[] args) {
        // 当前类(class)所在的包目录
        System.out.println(TestMain.class.getResource(""));
        // class path根目录
        System.out.println(TestMain.class.getResource("/"));
        
        // TestMain.class在<bin>/testpackage包中
        // 2.properties  在<bin>/testpackage包中
        System.out.println(TestMain.class.getResource("2.properties"));
        
        // TestMain.class在<bin>/testpackage包中
        // 3.properties  在<bin>/testpackage.subpackage包中
        System.out.println(TestMain.class.getResource("subpackage/3.properties"));
        
        // TestMain.class在<bin>/testpackage包中
        // 1.properties  在bin目录(class根目录)
        System.out.println(TestMain.class.getResource("/1.properties"));
    }
}

※Class.getResource和Class.getResourceAsStream在使用时,路径选择上是一样的。

2 :Class.getClassLoader().getResource(String path)的使用注意事项和示例

path不能以’/'开头时;path是从ClassPath根下获取;

path 为什么不能以 / 开头,因为classloader 不是用户自定义的类,所以没有相对路径的配置文件可以获取,所以默认都是从哪个classpath 路径下读取,自然就没有必要以 / 开头了。

还是先看一下下面这段代码的输出:

package testpackage;
public class TestMain {
    public static void main(String[] args) {
        TestMain t = new TestMain();
        System.out.println(t.getClass());
        System.out.println(t.getClass().getClassLoader());
        System.out.println(t.getClass().getClassLoader().getResource(""));
        System.out.println(t.getClass().getClassLoader().getResource("/"));//null
    }
}

输出结果:

class testpackage.TestMain
sun.misc.Launcher$AppClassLoader@1fb8ee3
file:/E:/workspace/Test/bin/
null

从结果来看【TestMain.class.getResource("/") == t.getClass().getClassLoader().getResource("")】

如果有同样的Project结构

使用Class.getClassLoader().getResource(String path)可以这么写:

package testpackage;

public class TestMain {
    public static void main(String[] args) {
        TestMain t = new TestMain();
        System.out.println(t.getClass().getClassLoader().getResource(""));
        
        System.out.println(t.getClass().getClassLoader().getResource("1.properties"));
        System.out.println(t.getClass().getClassLoader().getResource("testpackage/2.properties"));
        System.out.println(t.getClass().getClassLoader().getResource("testpackage/subpackage/3.properties"));
    }
}

※Class.getClassLoader().getResource和Class.getClassLoader().getResourceAsStream在使用时,路径选择上也是一样的。

3: class.getResource 和classloader.getResource()  原理:

1) class.getResouce 源码:

public java.net.URL getResource(String name) {
    name = resolveName(name);//这里返回classpath下的文件地址 ,不包含/
    ClassLoader cl = getClassLoader0();
    if (cl==null) {
        // A system class.
        return ClassLoader.getSystemResource(name);
    }
    return cl.getResource(name);//使用类加载器加载配置文件 ,name 是不包含/ 的路径
}

首先看看Class中的resolveName(String name)究竟是干什么的。源码如下所示:

 1  private String resolveName(String name) {
 2         if (name == null) {
 3             return name;
 4         }
 5         if (!name.startsWith("/")) {
 6             Class<?> c = this;
 7             while (c.isArray()) {
 8                 c = c.getComponentType();
 9             }
10             String baseName = c.getName(); //返回包括报名在内的类全路径
11             int index = baseName.lastIndexOf('.');
12             if (index != -1) {
13                 name = baseName.substring(0, index).replace('.', '/')
14                     +"/"+name;   //包名+相对路径拼接  刚好拼成classpath下具体文件的地址
15             }
16         } else {
17             name = name.substring(1); //去掉  / 符号,正好是classpath下具体文件的地址
18         }
19         return name;
20     }


下面我把这个源码讲的内容翻译人类语言:

给任意一个字符串name,如果该name是以/开始的,则该函数返回的是:去掉/这个字符的字符串。(如name="/ouyangfeng" 则调用该函数之后得到的结果是:name=ouyangfeng)。
如果该name这个字符串不是以/开始的,则该函数返回的结果是调用这个函数类所在的包名+name组成的字符串(例如假设Test5所在的包名是:com.qls.mount 。则:Test5.class.resolveName("ouyangfeng");返回结果是:com/qls/mount/ouyangfeng)

相当于返回了一个classpath 路径(这个路径是在根下的)

 我们看看classloader 的getResource方法: 这个方法的name参数已经是 “包名+文件名” 的格式了

public URL getResource(String name) {
    URL url;
    if (parent != null) {
        url = parent.getResource(name);
    } else {
        url = getBootstrapResource(name);
    }
    if (url == null) {
        url = findResource(name);
    }
    return url;
}

这段代码的意思是 先让父类加载器加载资源,如果父类加载器不存在,则使用boot 加载器加载,
如果父类加载器或者根加载器加载失败,则使用当前加载器加载配置文件。

protected URL findResource(String name) {
    return null;
}
这里的方法返回空,并且方法是protected 的,所以自定义类加载器的话,需要覆盖这个方法,
并且 系统类加载器和扩展类加载器肯定已经实现了这个方法,分别用来加载 用户自定义程序中classpath 路径下的文件 和 /lib/ext 下jar包内的配置文件

2 )Class类中的getResourceAsStream(String name)的源码如下:

1  public InputStream getResourceAsStream(String name) {
2         name = resolveName(name);//注意这里有一个resolveName(String name)方法,根据上述的分析,易知道这个源码的意思.
3         ClassLoader cl = getClassLoader0();
4         if (cl==null) {
5             // A system class.
6             return ClassLoader.getSystemResourceAsStream(name);
7         }
8         return cl.getResourceAsStream(name);
9     }


getClassLoader0() 这依据啥意思不太懂。

其中 ClassLoader中的getResourceAsStream(String name)中的源码如下:

1  public InputStream getResourceAsStream(String name) {
2         URL url = getResource(name);
3         try {
4             return url != null ? url.openStream() : null;//这句代码的意思是:如果url不是null时返回的是:url.openStream(),反之如果url为null则返回null.
5         } catch (IOException e) {
6             return null;
7         }
8     }

ClassLoader.getResourceAsStream(fileName)获取的流对象:

  如果filename 是本工程文件 :java.io.bufferedInputStream   

  如果file是jar包中文件:sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream

Java 应用读取 jar 包中的文件https://blog.csdn.net/antony1776/article/details/89249509

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值