.getResource()方法加载资源原理探析

2,736 阅读3分钟

1.getResource()获取文件路径方法

在编写java程序的时候,有时候需要从resource文件夹中加载资源文件。这时可以使用以下方式来获取资源文件路径:

TheClass.class.getResource(java.lang.String).getPath();

or

TheInstanceOfTheClass.getClass().getResource(java.lang.String).getPath();

2.getResource()源码以及官方文档方法说明

有关方法说明,官方文档链接点这里java.lang.Class#getResource方法源码(其中传入参数(java.lang.String)name为希望加载的资源文件名称):

    public java.net.URL getResource(String name) {
        name = resolveName(name);
        ClassLoader cl = getClassLoader0();
        if (cl==null) {
            // A system class.
            return ClassLoader.getSystemResource(name);
        }
        return cl.getResource(name);
    }

resolveName(name)解析资源文件名称:

    private String resolveName(String name) {
        if (name == null) {
            return name;
        }
        if (!name.startsWith("/")) {
            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 {
            name = name.substring(1);
        }
        return name;
    }

其中name参数格式有两种方式:

  • 如果参数以'/' ('\u002f')符号开始则执行name.substring(1),即资源文件名称实际为'/' ('\u002f')符号后面的部分。ex:若name为“/config.config”则文件名称为config.config

  • 否则,resolveName(String name)会找到这个类的名称,把其中“ /”('\u002f')代替名称中的“.” ('\ u002e')符号,接着拼接传入的name参数。因此传入的资源文件名称name为以下格式modified_package_name/name。其中modified_package_name为此资源文件相对于此类的包名,用“ /”('\u002f')代替“.” ('\ u002e')。ex:若TheClass类文件在com.example包中,资源文件相对于类路径为/config的时候,应传入name为config/config.config,经过resolveName(name)方法处理后返回name为com/example/config/config.config

关于搜索资源文件的规则,由该类的定义类加载器实现。 首先,此方法委托给这个类的类加载器,但是当这个类的加载器为引导类加载器时候,此方法就会委托给 ClassLoader.getSystemResource(java.lang.String)方法。经过类加载器将名称转换为文件名,然后从文件系统中读取该资源文件。

3.类加载器加载资源文件

有关类加载器说明,官方文档链接点这里java.lang.ClassLoader#getResource方法源代码(此方法返回值为:1.资源文件对象,2.当调用没有足够权限或者没有找到的情况下为null):

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

ClassLoader类使用委托模型来搜索类和资源。类加载器的每个实例都有一个关联的父类加载器。当请求查找类或资源时,类加载器实例将把对类或资源的搜索委托给其父类加载器,然后再尝试查找类或资源本身。 虚拟机的内置类加载器称为“引导类装入器”,它本身没有父类加载器,但可以作为类加载器实例的父类。(下图为类加载器委托模型,来源:www.geeksforgeeks.org/classloader-in-java/,侵删)

类加载器委托模型

4.示例

示例目录结构:

    |-- Java
        |-- src
            |-- main
                |-- java
                    |-- example
                        |-- GetResource.java
                |-- resources
                    |-- example
                        |-- config.config

    public static void main(String[] args) {
        System.out.println(
                GetResource.class.getResource(
                        "config.config"
                ).getPath()
        );
    }

结果为:

D:/work/myproject/springBoot/HelloWorldApp/Java/target/classes/example/config.config

debug的大致调用顺序为:

1.java.lang.Class#getResource=>
2.java.lang.ClassLoader#getResource=>
3.java.lang.ClassLoader#getResource=>
4.java.lang.ClassLoader#getBootstrapResource=>
5.java.lang.ClassLoader#getResource=>
6.java.net.URLClassLoader#findResource=>
7.java.lang.ClassLoader#getResource=>
8.java.lang.ClassLoader#getResource=>
9.java.net.URLClassLoader#findResource(命中)=>
10.java.lang.ClassLoader#getResource=>
11.java.lang.Class#getResource

5.总结

1.getResource()方法中参数有两种格式

2.getResource()此方法委托类加载器加载资源

3.类加载器使用委托模型

6.reference

[1]docs.oracle.com/javase/8/do…

[2]www.geeksforgeeks.org/classloader…

[3]docs.oracle.com/javase/8/do…