彻底理解class.getResource()

最近加载文件的时候遇到了一个问题,很有意思! 具体看下面案例代码

public class TestClassLoader {
    public static void main(String[] args) {
        System.out.println(TestClassLoader.class.getResource("ehcache.xml"));
        System.out.println(TestClassLoader.class.getResource("/ehcache.xml"));
        System.out.println();
        System.out.println(TestClassLoader.class.getClassLoader().getResource("ehcache.xml"));
        System.out.println(TestClassLoader.class.getClassLoader().getResource("/ehcache.xml"));
    }
}
file:/C:/myroad/utalitityUtils/target/classes/com/zsk/java/
file:/C:/myroad/utalitityUtils/target/classes/

file:/C:/myroad/utalitityUtils/target/classes/
null

那这两种方式有什么区别呢?下面跟源码一探究竟。

源码分析

TestClassLoader.class.getResource("ehcache.xml")

 public java.net.URL getResource(String name) {
        name = resolveName(name);
        ClassLoader cl = getClassLoader0();// 获取加载该Class的ClassLoader,sun.misc.Launcher$AppClassLoader@18b4aac2
        if (cl==null) { //如果加载该Class的ClassLoader为null,则表示这是一个系统class
            // A system class.
            return ClassLoader.getSystemResource(name); //如果是系统class
        }
        return cl.getResource(name);//调用ClassLoader的getResource方法
    }

下面是ClassLoader的getResource方法

public URL getResource(String name) {
        URL url;
        if (parent != null) {//这里的parent为sun.misc.Launcher$ExtClassLoader@7d4793a8
            url = parent.getResource(name);//这里是一个递归调用,再次进入之后parent为null
        } else {
            url = getBootstrapResource(name);//到达系统启动类加载器
        }
        if (url == null) {//系统启动类加载器没有加载到,递归回退到第一次调用然后是扩展类加载器
            url = findResource(name);
        }
        return url;//最后如果都没有加载到,双亲委派加载失败,则加载应用本身自己的加载器。
    }

下面我们跟一下getClassLoader源码看一下调用过程:

System.out.println(TestClassLoader.class.getClassLoader().getResource(""));

可以发现其实,Class.getResource和ClassLoader.getResource 最终调用的是ClassLoader 类的getResource方法。只不过Class.getResource是先调用Class 的 getResource 方法,在这个getResource 方法中,再去调用ClassLoader 类的getResource方法

那么Class类中的getResource方法做了什么呢,主要的一句是 name = resolveName(name);

 private String resolveName(String name) {
        if (name == null) {
            return name;
        }
        if (!name.startsWith("/")) {  //对于不以/开头的文件,
            Class<?> c = this;   //获取当前加载类的完整的类路径,我这里是com.zsk.java.TestClassLoader
            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 {
            name = name.substring(1);  //对于/开头的文件名,会只保留文件名称部分。
        }
        return name;
    }

TestClassLoader.class.getResource("")
Class类中的getResource方法返回的是com/zsk/java/

ClassLoader类中的getResource方法返回的是 file:/C:/myroad/utalitityUtils/target/classes/com/zsk/java/

TestClassLoader.class.getResource("/")
Class类中的getResource方法返回的是""

ClassLoader类中的getResource方法返回的是 file:/C:/myroad/utalitityUtils/target/classes/

JDK设置这样的规则,是很好理解的,path不以'/'开头时,我们就能获取与当前类所在的路径相同的资源文件,而以'/'开头时可以获取ClassPath根下任意路径的资源。

TestClassLoader.class.getClassLoader().getResource("")
ClassLoader类中的getResource方法返回的是 file:/C:/myroad/utalitityUtils/target/classes/

TestClassLoader.class.getClassLoader().getResource("/")
ClassLoader类中的getResource方法返回的是 null

对于ClassLoader.getResource, 直接调用的就是ClassLoader 类的getResource方法,那么对于getResource(""),path不以'/'开头时,首先通过双亲委派机制,使用的逐级向上委托的形式加载的,最后发现双亲没有加载到文件,最后通过当前类加载classpath根下资源文件。对于getResource("/"),'/'表示Boot ClassLoader中的加载范围,因为这个类加载器是C++实现的,所以加载范围为null。

类加载器ClassLoader

类加载器(ClassLoader)

我们都知道 Java 文件被运行,第一步,需要通过 javac 编译器编译为 class 文件;第二步,JVM 运行 class 文件,实现跨平台。而 JVM 虚拟机第一步肯定是 加载 class 文件,所以,类加载器实现的就是

通过一个类的全限定名来获取描述此类的二进制字节流

类加载器有几个重要的特性:

  • 每个类加载器都有自己的预定义的搜索范围,用来加载 class 文件;
  • 每个类和加载它的类加载器共同确定了这个类的唯一性,也就是说如果一个 class 文件被不同的类加载器加载到了 JVM 中,那么这两个类就是不同的类,虽然他们都来自同一份 class 文件;
  • 双亲委派模型。

双亲委派模型

  1. 所有的类加载器都是有层级结构的,每个类加载器都有一个父类类加载器(通过组合实现,而不是继承),除了启动类加载器(Bootstrap ClassLoader);
  2. 当一个类加载器接收到一个类加载请求时,首先将这个请求委派给它的父加载器去加载,所以每个类加载请求最终都会传递到顶层的启动类加载器,如果父加载器无法加载时,子类加载器才会去尝试自己去加载;

通过双亲委派模型就实现了类加载器的三个特性:

  1. 委派(delegation):子类加载器委派给父类加载器加载;
  2. 可见性(visibility):子类加载器可访问父类加载器加载的类,父类不能访问子类加载器加载的类;
  3. 唯一性(uniqueness):可保证每个类只被加载一次,比如 Object 类是被 Bootstrap ClassLoader 加载的,因为有了双亲委派模型,所有的 Object 类加载请求都委派到了 Bootstrap ClassLoader,所以保证了只被加载一次。

以上就是类加载器的一些特性,那么在 Java 中类加载器是如何实现的呢?

Java 中的类加载器

从 JVM 虚拟机的角度来看,只存在两种不同的类加载器:

  1. 启动类加载器(Bootstrap ClassLoader),是虚拟机自身的一部分;
  2. 所有其他的类加载器,独立于虚拟机外部,都继承自抽象类 java.lang.ClassLoader。

而绝大多数 Java 应用都会用到如下 3 中系统提供的类加载器:

  1. 启动类加载器(Bootstrap/Primordial/NULL ClassLoader):顶层的类加载器,没有父类加载器。负责加载 /lib 目录下的,或则被 -Xbootclasspath 参数所指定路径中的,并被 JVM 识别的(仅按文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录也不会被加载)类库加载到虚拟机内存中。所有被 Bootstrap classloader 加载的类,它的 Class.getClassLoader 方法返回的都是 null,所以也称作 NULL ClassLoader。
  2. 扩展类加载器(Extension CLassLoader):由 sun.misc.Launcher$ExtClassLoader 实现,负责加载 <JAVA_HOME>/lib/ext 目录下,或被 java.ext.dirs 系统变量所指定的目录下的所有类库;
  3. 应用程序类加载器(Application/System ClassLoader):由 sun.misc.Launcher$AppClassLoader 实现。它是 ClassLoader.getSystemClassLoader() 方法的默认返回值,所以也称为系统类加载器(System ClassLoader)。它负责加载 classpath 下所指定的类库,如果应用程序没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

如下,就是 Java 程序中的类加载器层级结构图:

image.png

总结

最后进行一个总结,Class.getResource和ClassLoader.getResource的区别,就是在加载资源文件的时候,加载方式的不同

class.getResource("/") == class.getClassLoader().getResource("")
其实,Class.getResource和ClassLoader.getResource本质上是一样的,都是使用ClassLoader.getResource加载资源的

Class.getResource真正调用ClassLoader.getResource方法之前,会先获取文件的路径(path不以'/'开头时,默认是从此类所在的包下取资源;path以'/'开头时,则是从项目的ClassPath根下获取资源) 加了/就表示classpath 不加就表示当前路径 很好理解

Class.getResource可以认为是一种简单的包装,最终用到的都是ClassLoader.getResource

ClassLoader.getResource方法会通过双亲委派机制,先委派双亲去加载类,如果双亲没有加载到,则再由自己加载。
ClassLoader.getResource以’/'开头返回的都是null。

查看mysql连接数

show full processlist

image.png

lsof -i:60174

COMMAND   PID   USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
java    14901 xxx   43u  IPv6 0x493120c1dd06ff41      0t0  TCP 10.211.55.2:60174->server-2:mysql (ESTABLISHED)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容

  • 虚拟机把描述类的数据从Class文件加载到内存, 并对数据进行校验、转换解析和初始化, 最终形成可以被虚拟机直接使...
    好好学习Sun阅读 1,155评论 0 3
  • 一. 前提 最近在做一个基础组件项目刚好需要用到JDK中的资源加载,这里说到的资源包括类文件和其他静态资源,刚好需...
    Java_苏先生阅读 318评论 0 1
  • ClassLoader翻译过来就是类加载器,普通的java开发者其实用到的不多,但对于某些框架开发者来说却非常常见...
    时待吾阅读 1,028评论 0 1
  • 金黄的叶承载着初秋的雨 这连绵的雨 好似被风吹散的泪水 滴落在这洼地 遗失去的 是那内心的火热 把衣物裹的更紧些 ...
    我言风中阅读 271评论 0 2
  • 1,从本篇文章/音频/视频中我学到的最重要的概念 条件状语从句 条件状语从句就是用以表示“假设条件下会。。。...
    土一13郝瑾珂阅读 278评论 1 0