首页 > 其他分享 >getResourceAsStream()

getResourceAsStream()

时间:2022-08-18 10:13:41浏览次数:45  
标签:getResourceAsStream name 路径 properties 调用 class

前言
项目中经常会使用 properties 文件定义一些配置变量,相应的就需要写一个类来加载此配置。
常用的方式是使用 class 或者 classLoader 对象的getResourceAsStream 来加载properties文件。
eg:

GlobalConfig.class.getResourceAsStream("/properties/globalConfig.properties")
GlobalConfig.class.getClassLoader().getResourceAsStream("properties/globalConfig.properties")

用Class或者ClassLoader 读取的区别是什么呢?相信眼尖的读者已经看出来了,就是指定的路径差了个 /。

路径是否有 “/” 到底有什么影响,能不能使用 GlobalConfig.class.getResourceAsStream(“properties/globalConfig.properties”)
或者 GlobalConfig.class.getClassLoader().getResourceAsStream("/properties/globalConfig.properties") 来读取配置呢?

带着种种疑问,下面就来探究一下这里面有什么猫腻。
测试环境:sping-boot项目(jar包)
项目结构:

深入源码探究:

Class类的 getResourceAsStream方法
查看Class类getResourceAsStream方法的源码,如下:

public InputStream getResourceAsStream(String name) {
name = resolveName(name);
ClassLoader cl = getClassLoader0();
//...
return cl.getResourceAsStream(name);
}

哦呵,看出来了吧,Class的getResourceAsStream方法调用的是ClassLoader的getResourceAsStream,Class这家伙真是够懒的。不过呢,调用之前也不是什么都不做,在调用前对name做了一些操作,即resolveName(name)。看来也不是真懒。
我们看看 resolveName 方法做了什么呢。

/**
* Add a package name prefix if the name is not absolute
* Remove leading "/" if name is absolute
*/
private String resolveName(String name) {
//...
if (!name.startsWith("/")) { // name不是以 "/" 开头
Class<?> c = this;
while (c.isArray()) {
c = c.getComponentType();
}
String baseName = c.getName(); // 当前类的全限定名。eg:com.markix.config.GlobalConfig
int index = baseName.lastIndexOf('.');
if (index != -1) {
name = baseName.substring(0, index).replace('.', '/') +"/"+name;
}
} else { // name以 "/" 开头
name = name.substring(1);
}
return name;
}

其实,resolveName方法上的注释已说明一切,硬翻译一波:**如果name不是绝对路径,则添加包路径;如果name是绝对路径,则删除最前面的"/"。**最终都会变成一个相对路径。

通过代码我们也能得出此结论,当name以‘/’开头,则执行 name = name.substring(1); 也就是截取掉开头的’/’。当name不是以“/”开头,则获取当前类class对象的name(即类的全路径名,包括包名),再截取包名替换成路径形式拼接到name的前缀。

举个栗子直观描述上面说的一坨东西:

绝对路径:GlobalConfig.class.getResourceAsStream("/properties/config.properties")
name 经过 resolveName 方法从 /properties/config.properties 变成 properties/config.properties
进而调用 ClassLoader类的getResourceAsStream(“properties/config.properties”)

相对路径:GlobalConfig类class.getResourceAsStream(“properties/config.properties”)
name 经过 resolveName 方法从原本的 properties/config.properties 变成 com/markix/config/properties/config.properties
进而调用 ClassLoader类的getResourceAsStream(“com/markix/config/properties/config.properties”)

小结
调用 类名.class.getResourceAsStream("/路径") 等价于调用 类名.class.getClassLoader().getResourceAsStream("路径")
调用 类名.class.getResourceAsStream("路径") 等价于调用 类名.class.getClassLoader().getResourceAsStream("类路径 + 路径")
过渡一句,class的getResourceAsStream本质就是调用classLoader的getResourceAsStream,下面探究下classLoader的getResourceAsStream。

ClassLoader类的getResourceAsStream方法
查看ClassLoader类getResourceAsStream方法的源码,呃,遇到难题了,有多个实现类重写了getResourceAsStream,到底是哪个类?

这里关乎Java类加载器的知识,不了解的请先自觉补姿势。博主直接抛结论啦,一般我们应用类运行都是使用 AppClassLoader 加载的,其继承自 URLClassLoader,所以我们查看URLClassLoader的getResourceAsStream方法,如下:

public InputStream getResourceAsStream(String name) {
URL url = getResource(name);
try {
if (url == null) {
return null;
}
URLConnection urlc = url.openConnection();
InputStream is = urlc.getInputStream();
//...
return is;
} catch (IOException e) {
return null;
}
}

核心就是调用了 getResource 方法,接着获取流就返回了。继续看getResource方法,在ClassLoader类中:

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;
}

和类加载类似,采用双亲委托。如果有parent,就先调用父加载器的方法。

我们的资源在我们项目中,其实最终调用的是findResource方法进行查找,代码如下:

public URL findResource(final String name) {
/*
* The same restriction to finding classes applies to resources
*/
URL url = AccessController.doPrivileged(
new PrivilegedAction<URL>() {
public URL run() {
return ucp.findResource(name, true);
}
}, acc);

return url != null ? ucp.checkURL(url) : null;
}

点到为止哈哈,有兴趣自行debug哈(再深入编不下去了哈哈)。通过debug调试,总结一下结论。

小结
调用 类名.class.getClassLoader().getResourceAsStream("/路径") ,总是返回 null。’/’ 不可访问。
调用 类名.class.getClassLoader().getResourceAsStream("路径") ,会在运行时环境(ClassPath路径)下搜索指定的“路径”。(也会在依赖jar包中搜索)
举个栗子
我的项目存放在 E:\WorkSpace\IDEA\spring-boot-demo,
编译目录为 E:\WorkSpace\IDEA\spring-boot-demo\target\classes\。(这个即为ClassPath路径)
我是直接在IDEA运行的,运行时会加载编译目录的内容,当调用
GlobalConfig.class.getClassLoader().getResourceAsStream("properties/globalConfig.properties") 时,也就会在 E:\WorkSpace\IDEA\spring-boot-demo\target\classes\ 目录查找一下 properties\globalConfig.properties 文件是否存在,找不到则报错,找得到就返回了。

总结
Class.getResourceAsStream 本质就是调用 ClassLoader.getResourceAsStream。

调用 类名.class.getResourceAsStream("/路径") 等价于调用 类名.class.getClassLoader().getResourceAsStream("路径")
调用 类名.class.getResourceAsStream("路径") 等价于调用 类名.class.getClassLoader().getResourceAsStream("类路径 + 路径")
ClassLoader.getResourceAsStream 会在运行时环境(ClassPath路径)下搜索指定的“路径”。(该路径必须是相对路径,绝对路径总是返回 null。’/’ 不可访问。)

另外:tomcat的特殊处理
在tomcat容器环境中,调用类名.class.getClassLoader().getResourceAsStream("/路径") 并不是返回null。原因在于tomcat重写了ClassLoader机制,war项目运行时并不是使用AppClassLoader加载,而是使用tomcat自定义的WebappClassLoader类,其父类WebappClassLoaderBase重写了getResourceAsStream方法,对路径做了特殊处理,最终实现了调用 类名.class.getClassLoader().getResourceAsStream("/路径") 等价于调用 类名.class.getClassLoader().getResourceAsStream("路径") 。
详见 WebappClassLoaderBase的getResourceAsStream方法。
————————————————
版权声明:本文为CSDN博主「markix」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_31772441/article/details/106413808

标签:getResourceAsStream,name,路径,properties,调用,class
From: https://www.cnblogs.com/wangfei-xsy/p/16597720.html

相关文章