分析Class类和ClassLoader类下的同名方法getResourceAsStream
在读取本地资源的时候我们经常需要用到输入流,典型的场景就是使用Druid连接池时读取连接池的配置文件。Java为我们提供了读取资源的方法getResourceAsStream(),该方法有三种:
- Class类中的
- ClassLoader类中的
- ServletContext接口中的
本文主要利用Class类和ClassLoader类中的方法进行对比
一、API
- Class类中的:

根据指定的名称查找资源,查找资源的规则是由定义此类的类加载器实现的,这个方法委托此对象的类加载器.如果这个对象是由bootstrap加载器加载的,这个方法就委托给ClassLoader.getSystemResourceAsStream(java.lang.String)
- ClassLoader类中的:

返回读取指定资源的输入流
根据API的介绍我们知道了,其实Class类中的getResourceAsStream方法其实最终是执行了ClassLoader中的同名方法。这点下文会根据源码分析。
关于类加载器的相关知识建议大家阅读深入理解类加载器。
二、具体分析
API中的介绍到Class类的getResourceAsStream方法是委托了类加载器实现的,那么这个方法还有什么存在的意义呢,直接调用类加载器中的方法不就得了吗? 当然,这两个方法不是完全相同的,区别在于二者搜索的范围不同。下面我们就具体的演示一下。
测试类:
public class Demo {
public static void main(String[] args) {
System.out.println(Demo.class.getResource(""));
System.out.println(Demo.class.getClassLoader().getResource(""));
//getResourceAsStream返回的流对象的地址值,打印出来不够直观,此处使用getResource方法代替
//URL路径更直观,要表达的意思也是一样的.
}
}
输出结果:
file:/D:/IdeaProjects/Test/out/production/resource/com/test/
file:/D:/IdeaProjects/Test/out/production/resource/
不难发现Class对象的搜索路径就是当前类所在的路径,而ClassLoader对象的搜索路径是src目录(也就是classpath)
而如果我们做一个小小的调整:
public class Demo {
public static void main(String[] args) {
System.out.println(Demo.class.getResource("/"));
System.out.println(Demo.class.getClassLoader().getResource(""));
}
}
输出结果就会变为:
file:/D:/IdeaProjects/Test/out/production/resource/
file:/D:/IdeaProjects/Test/out/production/resource/
两者的查找路径相同了,也就是class.getResource("/")的查找路径和classLoader.getResource("")的路径相同,同理getResourceAsStream也是一样。
对于Class.getResourceAsStream
- 当传入的path不含"/"时,查找资源是在该类所在的包下查找的
- 当传入的path含有"/"是,查找资源是从src目录,也即classpath下查找的
- "/"在这里就代表classpath
对于ClassLoader.getResourceAsStream
- 当传入的path不含"/"时,查找资源是从src目录下查找的
- 当传入的path有"/"时,结果为null
结果是null是因为path代表的是类加载器的加载范围,由于类加载器的委派机制,会层层委托到BootStrap类加载器,而“/”就代表BootStrap的加载范围,由于BootStrap类加载器是由C实现的而并非Java,所以加载范围是null
看到这里,大家肯定又有疑问,既然"/"代表BootStrap的加载范围,结果是null,为什么class类的方法中传入"/"没有出现null呢?这是因为在源码中对Class类中方法传入的path进行了处理,请看源码:
- Class类中的getResourceAsStream方法
public InputStream getResourceAsStream(String name) {
name = resolveName(name);//对传入的path进行处理
ClassLoader cl = getClassLoader0();//获取当前类的类加载器对象
if (cl==null) {
// A system class.
return ClassLoader.getSystemResourceAsStream(name);
}
return cl.getResourceAsStream(name);//调用ClassLoader类的同名方法
}
- Class类中的resolveName方法
private String resolveName(String name) {
if (name == null) {
return name;
}
if (!name.startsWith("/")) {//传入的path不是以"/"开头的情况
Class<?> c = this;
while (c.isArray()) {//数组类型的处理方法
c = c.getComponentType();
}
String baseName = c.getName();
int index = baseName.lastIndexOf('.');
if (index != -1) {
name = baseName.substring(0, index).replace('.', '/')
+"/"+name;
}
} else {//如果path以"/"开头,就把"/"截掉
name = name.substring(1);
}
return name;
}
至此,大家应该懂了Class和ClassLoader类中的getResourceAsStream方法,本质上来看其实是一样的,都是调用ClassLoader的getResourceAsStream方法.
而路径的区别是因为Class类的方法对传入的path进行了处理:如果"/"开头就从相对于classpath的绝对路径下查找资源;如果不以"/"开头,那就从当前类所在的相对路径下查找资源。
三、补充
接下来简要介绍下ServletContext接口中的getResourceAsStream方法。
显然,这个方法是作用在web项目中的,所以这个方法的搜索路径根路径就不是src目录了,而是web目录。以IDEA的项目结构为例

在Demo1中我们测试一下:
@WebServlet(urlPatterns="/demo1", name="Demo1")
public class Demo1 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext servletContext = getServletContext();
System.out.println(servletContext.getRealPath(""));
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
//通过浏览器访问此servlet时,得到控制台输出为:
//D:\IdeaProjects\Test\out\artifacts\test_war_exploded\
也就是默认的搜索路径是web项目下,此时要想访问src的资源怎么办呢?
答案是访问WEB-INF文件夹下的classes文件夹,也就是web项目中,classpath不再是普通java项目中的编译后的out\production\test了, 而是在WEB-INF/classes
因为IDEA会在我们的项目编译时帮我们把开发工具中的目录结构进行调整,转换为服务器可以进行部署的标准目录结构,也就是把src下目录结构 在编译后放进了classes文件夹中
所以在web项目中我们要访问资源就可以写相对于web文件夹的相对路径,只需注意src中的资源在WEB-INF/classes下就行了.
The end.
分析Class类和ClassLoader类下的同名方法getResourceAsStream的更多相关文章
- Class类和ClassLoader类的简单介绍
反射机制中的Class Class内部到底有什么呢?看下图! 代码: Class cls=Person.class; 1.Class类: 1. 对象照镜子后可以得到的信息:某个类的数据成员名,方法和构 ...
- 反射(学习整理)----Class类和加载器ClassLoader类的整理
1.学习反射的时整理的笔记!Class类和ClassLoader类的简单介绍 反射机制中的Class Class内部到底有什么呢?看下图! 代码: Class cls=Person.class; .C ...
- Spring Boot 2.x 启动全过程源码分析(上)入口类剖析
Spring Boot 的应用教程我们已经分享过很多了,今天来通过源码来分析下它的启动过程,探究下 Spring Boot 为什么这么简便的奥秘. 本篇基于 Spring Boot 2.0.3 版本进 ...
- JAVA基础加强(张孝祥)_类加载器、分析代理类的作用与原理及AOP概念、分析JVM动态生成的类、实现类似Spring的可配置的AOP框架
1.类加载器 ·简要介绍什么是类加载器,和类加载器的作用 ·Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader ...
- java笔记--理解java类加载器以及ClassLoader类
类加载器概述: java类的加载是由虚拟机来完成的,虚拟机把描述类的Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成能被java虚拟机直接使用的java类型,这就是虚拟机的类加载机制 ...
- List 接口以及实现类和相关类源码分析
List 接口以及实现类和相关类源码分析 List接口分析 接口描述 用户可以对列表进行随机的读取(get),插入(add),删除(remove),修改(set),也可批量增加(addAll),删除( ...
- [Java]类的生命周期(下)类的初始化[转]
上接深入java虚拟机——深入java虚拟机(二)——类加载器详解(上),在上一篇文章中,我们讲解了类的生命周期的加载和连接,这一篇我们接着上面往下看. 类的初始化:在类的生命周期执行完加载和连接之后 ...
- Java运行时动态加载类之ClassLoader
https://blog.csdn.net/fjssharpsword/article/details/64922083 *************************************** ...
- ClassLoader 提供了两个方法用于从装载的类路径中取得资源:
转:http://cheneyph.iteye.com/blog/831721 ClassLoader 提供了两个方法用于从装载的类路径中取得资源: public URL getResource ( ...
随机推荐
- java之Spring(AOP)-Annotation实现添加切面
我们已经知道之前的切面添加方式(动态代理),是定义了一个实现了InvocationHandler接口的Handlerservice类,然后 在这个类内部写好切面逻辑,包括切面放置的位置,很显然下面的这 ...
- spring中配置quartz调用两次及项目日志log4j不能每天生成日志解决方法
在quartz中配置了一个方法运行时会连续调用两次,是因为加载两次,只需在tomcat的server.xml中修改配置 <Host name="www.xx.cn" appB ...
- Java多线程:生命周期,实现与调度
Java线程生命周期 Java线程实现方法 继承Thread类,重写run()方法 实现Runnable接口,便于继承其他类 Callable类替换Runnable类,实现返回值 Future接口对任 ...
- Java构造器:级联调用,调用兄弟构造器
级联调用: class Father{ Father(){ System.out.println("Father birth"); } public void announce() ...
- 重载new和delete来检测内存泄漏
重载new和delete来检测内存泄漏 1. 简述 内存泄漏属于资源泄漏的一种,百度百科将内存泄漏分为四种:常发性内存泄漏.偶发性内存泄漏.一次性内存泄漏和隐式内存泄漏. 常发性指:内存泄漏的代 ...
- 【转】IE浏览器快捷键大全
一般快捷键F11打开/关闭全屏模式 TAB循环的选择地址栏,刷新键和当前标签页 CTRL+F在当前标签页查询字或短语 CTRL+N为当前标签页打开一个新窗口 CTRL+P打印当前标签页 CTRL+A选 ...
- JS入门熟知
JS是面向对象的语言 封装 继承 多态 聚集(对象中具有引用其他对象的能力) JS使用中绝大多数情况不需要进行面向对象的设计,很多情况是使用已经设计好,准备好的对象,基于对象的语言. JS的使用(引入 ...
- python字符串基本编码
综述:python中字符串分为字节字符和非字节字符python3中默认输入字符串以非字节字符编码,使用unicode字符集表示,可以使用encode方法转化为ascii,utf-8, utf-16等各 ...
- Ubuntu 16.04 安装 Docker
在Ubuntu上安装Docker, 非常简单, 我测试过 16.04, 17.04, 以及最新版 18.04,都是可以成功安装,并使用的. 第一步: 启动root账号 第二步: 配置网络,能上网 ...
- [ 搭建Redis本地服务器实践系列 ] :序言
说起来,是在一个气候适宜的下午,虽然临近下班,不过办公室里还是充满了忙碌的身影,不时的还会从办公区传来小伙伴们为了一个需求而激烈争论的声音,自从入了互联网这个行业,说实话,也就很少休息了,当然了也不全 ...