一文搞懂class.getClassLoader().getResource("xx.xml")和class.getResource("xx.xml")的区别和联系

声明:本文转载,原文链接

一文搞懂class.getClassLoader().getResource("xx.xml")和class.getResource("xx.xml")的区别和联系 - 简书  https://www.jianshu.com/p/e3c94eff77c5

前言

我们在阅读一些开源框架的时候,会发现有些开源框架在读取配置文件采用的是class.getClassLoader().getResource("xx.xml")这种方式,还有一些采用的是class.getResource("xx.xml")。那么此时我们可能就产生了一些疑问,这两种读取资源的方式有什么不同呢?现在我们就解析一下相关的源码,看看他们之间的区别和联系。

类加载器科普

我们知道JVM的类加载器是分为四大块的,分别是BootStrapClassLoader,extClassLoader以及AppClassLoader,最后就是我们自己自定义的ClassLoader。
他们之间的父子关系通过下面的代码可以进行验证(自定义类加载器的父加载器是AppClassLoader,由于自定义类加载器不是本文的重点,就不加以验证,感兴趣的小伙伴可以自行验证)

public class ResourceTest {
    public static void main(String[] args) {
        ClassLoader classLoader = ResourceTest.class.getClassLoader();
        //通过返回结果可以看到自己写的类默认是由applicationClassLoader加载的
        System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
        //类加载器是有父子关系的,appClassLoader的父加载器是extClassLoader
        classLoader = classLoader.getParent();
        System.out.println(classLoader);//sun.misc.Launcher$ExtClassLoader@61bbe9ba
        //类加载器是有父子关系的,extClassLoader的父加载器是BootStrapClassLoader,但是BootStrapClassLoader是C语言写的,所以会返回null
        classLoader = classLoader.getParent();
        System.out.println(classLoader);//null
    }
}

这时候我们可能就会有一个疑问,这些不同的类加载器都是加载哪些java文件呢?
其实这些在sun.miscLauncher类里就有答案,现在我们打开这个类来看下。

1.bootStrapClassLoader

上图中红色箭头指定的路径就是bootstrapClassLoader类加载器可以加载的类的路径
我们通过代码演示一下都有哪些类是属于bootStrapClassLoader来加载的

public class ClassLoaderTest {
    public static void main(String[] args) {
        String str = System.getProperty("sun.boot.class.path");
        Arrays.stream(str.split(";")).forEach(s -> System.out.println(s));
        /*
        C:Program FilesJavajdk1.8.0_91jrelib
esources.jar
        C:Program FilesJavajdk1.8.0_91jrelib
t.jar
        C:Program FilesJavajdk1.8.0_91jrelibsunrsasign.jar
        C:Program FilesJavajdk1.8.0_91jrelibjsse.jar
        C:Program FilesJavajdk1.8.0_91jrelibjce.jar
        C:Program FilesJavajdk1.8.0_91jrelibcharsets.jar
        C:Program FilesJavajdk1.8.0_91jrelibjfr.jar
        C:Program FilesJavajdk1.8.0_91jreclasses
         */
    }
}

2.extClassLoader

上面红色箭头指定的路径就是extClassLoader加载的路径,现在我们同样通过代码来看看这个类加载器主要加载哪些类。

import java.util.Arrays;

public class ClassLoaderTest {
    public static void main(String[] args) {
        String str = System.getProperty("java.ext.dirs");
        Arrays.stream(str.split(";")).forEach(s -> System.out.println(s));
        /*
        C:Program FilesJavajdk1.8.0_91jrelibext
        C:WINDOWSSunJavalibext
         */
    }
}

3.appClassLoader

上面红色箭头就是appClassLoader加载的类的路径,这个加载器跟我们打的交道最多,因为我们写的类在不指定类加载器的情况下都是由它进行加载(正是因为如此,加入的第三方jar包也是由它加载,所以每个人在项目里加入的第三方jar包不同,加载的结果也不同,大家自行验证),下面我们同样通过代码去验证下它加载了哪些类。
import java.util.Arrays;

public class ClassLoaderTest {
    public static void main(String[] args) {
        String str = System.getProperty("java.class.path");
        Arrays.stream(str.split(";")).forEach(s -> System.out.println(s));
        /*
        C:Program FilesJavajdk1.8.0_91jrelibcharsets.jar
        C:Program FilesJavajdk1.8.0_91jrelibdeploy.jar
        C:Program FilesJavajdk1.8.0_91jrelibextaccess-bridge-64.jar
        C:Program FilesJavajdk1.8.0_91jrelibextcldrdata.jar
        C:Program FilesJavajdk1.8.0_91jrelibextdnsns.jar
        C:Program FilesJavajdk1.8.0_91jrelibextjaccess.jar
        C:Program FilesJavajdk1.8.0_91jrelibextjfxrt.jar
        C:Program FilesJavajdk1.8.0_91jrelibextlocaledata.jar
        C:Program FilesJavajdk1.8.0_91jrelibext
ashorn.jar
        C:Program FilesJavajdk1.8.0_91jrelibextsunec.jar
        C:Program FilesJavajdk1.8.0_91jrelibextsunjce_provider.jar
        C:Program FilesJavajdk1.8.0_91jrelibextsunmscapi.jar
        C:Program FilesJavajdk1.8.0_91jrelibextsunpkcs11.jar
        C:Program FilesJavajdk1.8.0_91jrelibextzipfs.jar
        C:Program FilesJavajdk1.8.0_91jrelibjavaws.jar
        C:Program FilesJavajdk1.8.0_91jrelibjce.jar
        C:Program FilesJavajdk1.8.0_91jrelibjfr.jar
        C:Program FilesJavajdk1.8.0_91jrelibjfxswt.jar
        C:Program FilesJavajdk1.8.0_91jrelibjsse.jar
        C:Program FilesJavajdk1.8.0_91jrelibmanagement-agent.jar
        C:Program FilesJavajdk1.8.0_91jrelibplugin.jar
        C:Program FilesJavajdk1.8.0_91jrelib
esources.jar
        C:Program FilesJavajdk1.8.0_91jrelib
t.jar
        G:codeSelfStudyjdk_studyout	estjdk_study
        G:codeSelfStudyjdk_studyoutproductionjdk_study
        C:Users闫尚.m2
epositoryjunitjunit4.12junit-4.12.jar
        C:Users闫尚.m2
epositoryorghamcresthamcrest-core1.3hamcrest-core-1.3.jar
        C:Program FilesJavajdk1.8.0_91libdt.jar
        C:Program FilesJavajdk1.8.0_91lib	ools.jar
        C:Program FilesJavajdk1.8.0_91libsa-jdi.jar
        C:Program FilesJavajdk1.8.0_91libjconsole.jar
        C:Program FilesJavajdk1.8.0_91libpackager.jar
        C:Program FilesJavajdk1.8.0_91libjavafx-mx.jar
        C:Program FilesJavajdk1.8.0_91libant-javafx.jar
        G:softidealibidea_rt.jar
         */
    }
}

验证环境

注意:这里主要是针对自定义类做的测试,自定义类默认的类加载器是appClassLoader,如果你用String等JDK自带的类做测试的话,因为所属的类加载器不同,debug的结果也是不同的,请自行验证)

import java.net.URL;

public class ResourceTest {
    public static void main(String[] args) {
       URL url = ResourceTest.class.getClassLoader().getResource("application.xml");
        System.out.println(url);
       url = ResourceTest.class.getResource("application.xml");
        System.out.println(url);
/*
*  结果;
file:/G:/code/SelfStudy/jdk_study/out/production/jdk_study/application.xml
file:/G:/code/SelfStudy/jdk_study/out/production/jdk_study/application.xml
*/
    }
}

源码分析

有了上面所科普的类加载器的相关知识,那么我们就以验证环境的代码来看看class.getClassLoader().getResource("application.xml")和class.getResource("application.xml")的区别到底在哪。

1.分析class.getClassLoader().getResource("application.xml")的源码

    public URL getResource(String name) {  //递归调用
        URL url;
        if (parent != null) {
            url = parent.getResource(name);
        } else {//递归调用获取BootStrapClassLoader的URL
            url = getBootstrapResource(name);
        }
        if (url == null) {
            url = findResource(name);
        }
        return url;
    }
getClassLoader().getResource("application.xml")调用的就是上面的方法,这个方法其实是个递归调用,首先获取当前类加载器的父加载器,如果父加载器不为空,继续递归调用这个方法,直到递归到父加载器为空终止。然后就会调用getBootstrapResource(name)方法,现在我们来看下这个方法
   private static URL getBootstrapResource(String name) {
        URLClassPath ucp = getBootstrapClassPath();
        Resource res = ucp.getResource(name);
        return res != null ? res.getURL() : null;
    }

getBootstrapResource这个方法又调用了getBootstrapClassPath()去获取路径,那么getBootstrapClassPath()是去哪里拿的路径呢?

  // Returns the URLClassPath that is used for finding system resources.
    static URLClassPath getBootstrapClassPath() {
        return sun.misc.Launcher.getBootstrapClassPath();
    }

看到sun.misc.Launcher.getBootstrapClassPath() 是不是豁然开朗?这就是我们前面分析的bootStrapClassLoader加载的路径啊!

OK,现在我们得出第一个结论:
getClassLoader().getResource("application.xml")首先会去根类加载器加载的路径下找application.xml文件。如果找到就直接返回application.xml对应的URL地址
那如果在根类加载器加载的路径下没有找到application.xml会怎么样呢?我们接着分析:当getBootstrapResource(name) 返回null的时候会接着调用 findResource(name),我们来看看这个方法debug的结果,如果不会debugJDK源码可以参考我的另一篇文章IDEA搭建JDK源码阅读环境

通过debug的结果我们发现如果bootStrapClassLoader加载的路径下没有找到application.xml就会去extClassLoader类加载器加载的路径下去找。

OK,这里我们得出第二个结论:
getClassLoader().getResource("application.xml")在根类加载器加载路径下找不到application.xml时就会去extClassLoader加载器加载的路径去找。
 
因为这里是递归调用,extClassLoader类加载器路径下没有找到application.xml,我们猜测此时应该就是到appClassLoader类加载路径下去找了。那么根据debug的结果发现与我们的预期一致。下面贴图

OK,此时我们得出第三个结论:

getClassLoader().getResource("application.xml")在extClassLoader加载器加载路径下找不到application.xml时就会去appClassLoader加载器加载的路径去找。如果appClassLoader加载路径下再找不到,直接返回null。

2.分析class.getResource("application.xml")的源码

    public java.net.URL getResource(String name) {
        name = resolveName(name);
        ClassLoader cl = getClassLoader0();//获取当前类的类加载器
        if (cl==null) {//bootStrapClassLoader
            // A system class.
            return ClassLoader.getSystemResource(name);
        }
        return cl.getResource(name);//调用的还是class.getClassLoader.getResouce(name)
    }
通过源码可以看出class.getResource(name)底层还是调用了class.getClassLoader().getResource(name),只是多了一步resolveName(name)的方法,那么我们就看看这个方法主要是做了什么操作。
private String resolveName(String name) {
        if (name == null) {//1.name = null  直接返回
            return name;
        }
        if (!name.startsWith("/")) {//2.1 name不以/开头
            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 { //2.2 name以/开头 就截取/之后的字符串
            name = name.substring(1);
        }
        return name;
    }

上面的代码一目了然,就是对name进行一系列的组装,组装完成后还是调用的class.getClassLoader().getResource(name)方法去查找对应的资源文件。

OK,此时我们得出结论:
通过resolveName(name)组装后得到的name参数值跟class.getClassLoader().getResource(name)中的name参数值一样的话,他们最后的输出结果没有任何区别。
 

结束语

通过源码的分析,我们就大概知道了class.getClassLoader().getResource("xx.xml")和class.getResource("xx.xml")的区别以及联系,因为时间有限,就不对其他情况进行分析验证了,大家可以通过上面源码分析的思路去自己验证,谢谢大家。



作者:负重前行丶
链接:https://www.jianshu.com/p/e3c94eff77c5
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文转载,原文链接

一文搞懂class.getClassLoader().getResource("xx.xml")和class.getResource("xx.xml")的区别和联系 - 简书  https://www.jianshu.com/p/e3c94eff77c5

原文地址:https://www.cnblogs.com/tongongV/p/13866609.html