在读取本地资源的时候我们经常需要用到输入流,典型的场景就是使用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的更多相关文章

  1. Class类和ClassLoader类的简单介绍

    反射机制中的Class Class内部到底有什么呢?看下图! 代码: Class cls=Person.class; 1.Class类: 1. 对象照镜子后可以得到的信息:某个类的数据成员名,方法和构 ...

  2. 反射(学习整理)----Class类和加载器ClassLoader类的整理

    1.学习反射的时整理的笔记!Class类和ClassLoader类的简单介绍 反射机制中的Class Class内部到底有什么呢?看下图! 代码: Class cls=Person.class; .C ...

  3. Spring Boot 2.x 启动全过程源码分析(上)入口类剖析

    Spring Boot 的应用教程我们已经分享过很多了,今天来通过源码来分析下它的启动过程,探究下 Spring Boot 为什么这么简便的奥秘. 本篇基于 Spring Boot 2.0.3 版本进 ...

  4. JAVA基础加强(张孝祥)_类加载器、分析代理类的作用与原理及AOP概念、分析JVM动态生成的类、实现类似Spring的可配置的AOP框架

    1.类加载器 ·简要介绍什么是类加载器,和类加载器的作用 ·Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader ...

  5. java笔记--理解java类加载器以及ClassLoader类

    类加载器概述: java类的加载是由虚拟机来完成的,虚拟机把描述类的Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成能被java虚拟机直接使用的java类型,这就是虚拟机的类加载机制 ...

  6. List 接口以及实现类和相关类源码分析

    List 接口以及实现类和相关类源码分析 List接口分析 接口描述 用户可以对列表进行随机的读取(get),插入(add),删除(remove),修改(set),也可批量增加(addAll),删除( ...

  7. [Java]类的生命周期(下)类的初始化[转]

    上接深入java虚拟机——深入java虚拟机(二)——类加载器详解(上),在上一篇文章中,我们讲解了类的生命周期的加载和连接,这一篇我们接着上面往下看. 类的初始化:在类的生命周期执行完加载和连接之后 ...

  8. Java运行时动态加载类之ClassLoader

    https://blog.csdn.net/fjssharpsword/article/details/64922083 *************************************** ...

  9. ClassLoader 提供了两个方法用于从装载的类路径中取得资源:

    转:http://cheneyph.iteye.com/blog/831721 ClassLoader 提供了两个方法用于从装载的类路径中取得资源: public URL  getResource ( ...

随机推荐

  1. 八爪鱼在哪里设置xpath

    分享:35个做好的爬虫规则+160篇图文教程汇总 一般在八爪鱼中,获取网页上某个元素的XPATH有以下几种方式:一.在内置浏览器中点选的操作,八爪鱼自动识别XPATH.但是有时候,自动识别的可能不准确 ...

  2. VueJs(12)---vue-router(导航守卫,路由元信息)

    vue-router(导航守卫,路由元信息) 之前泄露两篇有关vue-router博客: VueJs(10)---vue-router(进阶1) VueJs(11)---vue-router(进阶2) ...

  3. linux进程、线程与cpu的亲和性(affinity)

    参考:http://www.cnblogs.com/wenqiang/p/6049978.html 最近的工作中对性能的要求比较高,下面简单做一下总结: 一.什么是cpu亲和性(affinity) C ...

  4. PAT1035: Password

    1035. Password (20) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue To prepare f ...

  5. python+selenium实现登录账户

    selenium 是一套完整的web应用程序测试系统,包含了测试的录制(selenium IDE),编写及运行(Selenium Remote Control)和测试的并行处理(Selenium Gr ...

  6. linux下Clang和gcc的区别

    Clang 比 GCC 编译器的优势: 编译速度更快 编译产出更小 出错提示更友 好,比如 clang 在编译过程可以直接指出相对简单的出错位置以及它 “ 认为 ” 正确的方式 . 内置有静态分析工具 ...

  7. hessian在ssh项目中的配置

    一. 在服务端发布一个web项目 1.创建一个动态的web项目,并导入hessian的jar包 2. 在服务端的crm项目中创建接口 package cn.rodge.crm.service;impo ...

  8. Bash特殊变量:$0, $#, $*, $@, $?, $$实战

    在linux下配置shell参数说明 前面已经讲到,变量名只能包含数字.字母和下划线,因为某些包含其他字符的变量有特殊含义,这样的变量被称为特殊变量. 例如,$ 表示当前Shell进程的ID,即pid ...

  9. Linux时间子系统之八:动态时钟框架(CONFIG_NO_HZ、tickless)

    在前面章节的讨论中,我们一直基于一个假设:Linux中的时钟事件都是由一个周期时钟提供,不管系统中的clock_event_device是工作于周期触发模式,还是工作于单触发模式,也不管定时器系统是工 ...

  10. HTTP 前端需明白的相关知识点

    简介: http(Hyper Text Transfer Protocol)超文本传输协议是万维网应用层的协议,使用了面向连接的TCP作为运输层协议. 特征: 简单快速:通过url就可以访问资源,协议 ...