Java中资源文件获取源码浅析

Java中资源文件获取源码浅析

环境:JDK 1.8.0_191 JDK11.0.10

话不多说,上代码:

package com.snails.test;

import java.net.URL;
public class OperatorsTest {

    public static void main(String[] args) {
        URL resource = OperatorsTest.class.getResource("jdbc.properties");
        String path = resource.getPath();
        System.out.println(path);
    }
}

JDK11

Class.getResource(String)

首先进 Class.getResource 方法中看看:

public URL getResource(String name) {
    // 判断 name 是不是以/开头,
    // 1.如果不是则获取当前类(OperatorsTest)的包名,并将包名的.替换为/然后和name拼接 eg name = "com/snails/test" + "/" + name
    // 2. 如果是则name = substring(1) 去掉/
    name = resolveName(name);
	// 获取module
    Module thisModule = getModule();
    // 如果module是已命名的module
    if (thisModule.isNamed()) {
        // check if resource can be located by caller
        // 判断是不是.class文件或包名
        if (Resources.canEncapsulate(name)
            && !isOpenToCaller(name, Reflection.getCallerClass())) {
            return null;
        }

        // resource not encapsulated or in package open to caller
        String mn = thisModule.getName();
        // 获取类加载器AppClassLoader
        ClassLoader cl = getClassLoader0();
        try {
            if (cl == null) {
                return BootLoader.findResource(mn, name);
            } else {
                // 在父类加载器中查找资源文件,如果没找到则去BuiltinClassLoader类加载器路径中找
                return cl.findResource(mn, name);
            }
        } catch (IOException ioe) {
            return null;
        }
    }

    // unnamed module
    // 获取AppClassLoader类加载器
    ClassLoader cl = getClassLoader0();
    if (cl == null) {
        return ClassLoader.getSystemResource(name);
    } else {
        // 查找父类加载器路径中的资源文件是存在,不存在则去BuiltinClassLoader类加载器路径中查找
        // 通过name拿到module,如果不在模块包中,但可能在为此加载器定义的模块中
        // 查看资源的缓存是否存为空,为空则在所有模块中搜索资源
        return cl.getResource(name);
    }
}

从上面分析可以得到 Class.getResource(String) 方法首先会去当前模块的当前类所属包下去找资源文件,如果找不到则去其他模块同名包下找。

Class.getResourceAsStream(String) 查找流程和 Class.getResource(String) 方法是一样的,这里就不分析了,不同的是会将 URL 转化为 InputStream。

Class.getClassLoader().getResource(String)

public URL getResource(String name) {
    Objects.requireNonNull(name);
    URL url;
    if (parent != null) {
        // 去父类加载器路径中找
        url = parent.getResource(name);
    } else {
        // 去 jdk 的module中找
        url = BootLoader.findResource(name);
    }
    if (url == null) {
        // 先去 jdk module中找,找不到然后去classpath中找
        url = findResource(name);
    }
    return url;
}

image01

可以看到 Class.getClassLoader().getResource(String) 方法会先去 jdk 的 module 中找,找不到再去编译后的项目路径中找,所以这里的传参相对 Class.getResource(String) 更加灵活,可以查找项目中任意给定路径的资源。如查找 com.snails.resource 包下的 jdbc.properties:

URL resource = OperatorsTest.class.getClassLoader().getResource("com/snails/resource/jdbc.properties");

Class.getClassLoader().getResourceAsStream(String) 查找和 Class.getClassLoader().getResource(String) 一样,这里就不分析了,只不过将 URL 转为了 InputStream。

JDK 8

Class.getResource(String)

public java.net.URL getResource(String name) {
    // 判断 name 是不是以/开头,
    // 1.如果不是则获取当前类(OperatorsTest)的包名,并将包名的.替换为/然后和name拼接 eg name = "com/snails/test" + "/" + name
    // 2. 如果是则name = substring(1) 去掉/
    name = resolveName(name);
    // 获取类加载器AppClassLoader
    ClassLoader cl = getClassLoader0();
    if (cl==null) {
        // A system class.
        return ClassLoader.getSystemResource(name);
    }
    return cl.getResource(name);
}

ClassLoader

public URL getResource(String name) {
    URL url;
    if (parent != null) {
        // 去父类加载器路径中查找
        url = parent.getResource(name);
    } else {
        // 去 jdk/jre/lib和jdk/jre/classes 中查找
        url = getBootstrapResource(name);
    }
    if (url == null) {
        //父类中,没找到则去URLClassLoader类加载器路径中查找 根据项目编译后的路径+资源文件名
        url = findResource(name);
    }
    return url;
}

image01

所以资源是放在OperatorsTest类所属的包中则 Class.getResource(String) 的资源名不用带上包名:

URL resource = OperatorsTest.class.getResource("jdbc.properties");

如果资源是放在其它包中(如 com.snails.resource )那是不行的,resolveName 方法会给你拼接成这样:

com/snails/test/com/snails/resource

所以 Class.getResource(String) 只适合以下情况:

  • 查找资源和调用类同包

  • jdk/jre/lib或jdk/jre/classes下的包中有这个配置,比如自己手动打包一个jar,里面资源路径如下:

    getResource(“jdbc.properties”) :com/snails/test/jdbc.properties

    getResource(“com/snails/resource/jdbc.properties”) : com/snails/test/com/snails/resource/jdbc.properties

Class.getResourceAsStream(String) 方法和 Class.getResource(String) 方法查找过程是一样的,这里就不分析了,只不过将 URL 转为了 InputStream。

Class.getClassLoader().getResource(String)

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

Class.getClassLoader().getResource(String) 和 Class.getResource(String) 差异点在于 多了个 resolveName(name) 步骤,所以Class.getClassLoader().getResource(String) 的灵活性就更强,可以查找项目中任意给定路径的资源。如查找 com.snails.resource包下的 jdbc.properties:

URL resource = OperatorsTest.class.getClassLoader().getResource("com/snails/resource/jdbc.properties");

Class.getClassLoader().getResourceAsStream(String) 查找和 Class.getClassLoader().getResource(String) 一样,这里就不分析了,只不过将 URL 转为了 InputStream。

总结

​ 不论 jdk8 还是 jdk11 可以看到两者查找资源方式都是大同小异的,只不过 jdk11 模块化后是去模块中找,jdk8 则是去 jdk/jre/lib,jdk/jre/classes 中找

  • Class.getResource(String)/Class.getResourceAsStream(String) 查找当前包下和 jdk 环境中的资源
  • Class.getClassLoader().getResource(String)/Class.getClassLoader().getResourceAsStream(String) 查找项目指定路径下的资源

posted @ 2021-06-05 12:30  SnailsH  阅读(7)  评论(0编辑  收藏  举报  来源