• 说说Java中的资源文件的读取


    最近在看spring的资源获取时发现JDK里存在几种不同方式的资源获取,因比较混乱特地总结起来帮助和我一样混乱的人理解。下面是我项目的类结构图,在 src/main/java 下有两个类 ResourceTest.java和Resource.javaresources 目录下有两个资源文件 request.xml 和 conf/sysConf.json

    ├── pom.xml
    ├── src
    │   ├── main
    │   │   ├── java
    │   │   │   ├── com
    │   │   │   │   ├── alipay
    │   │   │   │   │   ├── ResourceTest.java
    │   │   │   │   │   └── Resource.java
    │   │   └── resources
    │   │   │   ├── conf
    │   │   │   │   ├── sysConf.json
    │   │   │   └── request.xml
    └── local.iml
    

    在ResourceTest中,我想获取Resource这个类以及request.xml、sysConf这两个资源文件,可以分为Class和ClassLoader两种方式来获取资源,而ClassLoader则又可以细分为3种方式:

    public class ResourceTest {
    
        public static void main(String[] args) {
            // 1、通过Class的getResource方法
            String a1 = ResourceTest.class.getResource("/com/alipay/Resource.class").getPath();
            String a2 = ResourceTest.class.getResource("Resource.class").getPath();
            String a3 = ResourceTest.class.getResource("/request.xml").getPath();
            String a4 = ResourceTest.class.getResource("../../request.xml").getPath();
            String a5 = ResourceTest.class.getResource("/conf/sysConf.json").getPath();
            String a6 = ResourceTest.class.getResource("../../conf/sysConf.json").getPath();
    
            // 2、通过本类的ClassLoader的getResource方法
            String b1 = ResourceTest.class.getClassLoader().getResource("com/alipay/Resource.class").getPath();
            String b2 = ResourceTest.class.getClassLoader().getResource("request.xml").getPath();
            String b3 = ResourceTest.class.getClassLoader().getResource("conf/sysConf.json").getPath();
    
            // 3、通过ClassLoader的getSystemResource方法
            String c1 = ClassLoader.getSystemClassLoader().getResource("com/alipay/Resource.class").getPath();
            String c2 = ClassLoader.getSystemClassLoader().getResource("request.xml").getPath();
            String c3 = ClassLoader.getSystemClassLoader().getResource("conf/sysConf.json").getPath();
    
            // 4、通过ClassLoader的getSystemResource方法
            String d1 = ClassLoader.getSystemResource("com/alipay/Resource.class").getPath();
            String d2 = ClassLoader.getSystemResource("request.xml").getPath();
            String d3 = ClassLoader.getSystemResource("conf/sysConf.json").getPath();
    
            // 5、通过Thread方式
            String e1 = Thread.currentThread().getContextClassLoader().getResource("com/alipay/Resource.class").getPath();
            String e2 = Thread.currentThread().getContextClassLoader().getResource("request.xml").getPath();
            String e3 = Thread.currentThread().getContextClassLoader().getResource("conf/sysConf.json").getPath();
        }
    }
    

    以上所有的方式都能够获取到对应的资源文件。

    由于maven打包会把 src/main/javasrc/main/resources 下的文件放到 target/classes 下,所以下面统一以根路径代表此目录,总结起来有以下几个规律:

    • Class.getResource()的资源获取如果以 / 开头,则从根路径开始搜索资源。
    • Class.getResource()的资源获取如果不以 / 开头,则从当前类所在的路径开始搜索资源。
    • ClassLoader.getResource()的资源获取不能以 / 开头,统一从根路径开始搜索资源。

    下面还是老习惯,翻开源码看看为什么是这样的规律。

    Class.getResource()

    public java.net.URL getResource(String name) {
        name = resolveName(name);
        // 获得类的类加载器,默认为AppClassLoader
        ClassLoader cl = getClassLoader0();
        if (cl==null) {
            // A system class.
            return ClassLoader.getSystemResource(name);
        }
        return cl.getResource(name);
    }
    

    可以很清晰的看出上面的资源获取流程:

    1. 解析文件路径,变成ClassLoader所支持的路径。
    2. 获取该类的类加载器,默认为AppClassLoader,接着调用它的getResource方法。
    3. 如果类加载器获取失败,直接走ClassLoader的getSystemResource方法来获取

    我们看看resolveName的解析规则:

    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();
            // 截取当前类所在的包和name使用 / 进行拼接
            int index = baseName.lastIndexOf('.');
            if (index != -1) {
                name = baseName.substring(0, index).replace('.', '/')
                        +"/"+name;
            }
        } else {
            // 如果以 / 开头,截取 / 后面的内容
            name = name.substring(1);
        }
        return name;
    }
    

    原理和我们上面分析的一样。因为Class的getResource最终还是调用的ClassLoader,所以我们接着来看ClassLoader的相关资源获取方法。

    ClassLoader.getResource()

    public URL getResource(String name) {
        URL url;
        if (parent != null) {
            // 递归调用
            url = parent.getResource(name);
        } else {
            // 使用BootstrapClassLoader发现资源
            url = getBootstrapResource(name);
        }
        if (url == null) {
            // 真正去找对应的url
            url = findResource(name);
        }
        return url;
    }
    

    这个方法比较有意思的地方在于它使用了双亲委派机制来加载资源(回顾双亲委派机制 点我 ),它从BootstrapClassLoader一层层往下找直到最后找到该资源。本例中是通过AppClassLoader来找到了对应的资源(实际使用了URLClassLoader的findResource方法)

    ClassLoader.getSystemResource()

    这种方式对应于例子中的方式四,相比于方式三就多了一个空判断。

    public static URL getSystemResource(String name) {
        // systemClassLoader就是从Launcher获取的AppClassLoader
        ClassLoader system = getSystemClassLoader();
        if (system == null) {
            return getBootstrapResource(name);
        }
        return system.getResource(name);
    }
    

    getSystemResource和getResource的区别就在于你是否实现了自己的类加载器,如果都是使用的默认的AppClassLoader,这两个方法的作用一样。

    线程上下文加载方式

    这种加载方式对应于例子中的最后一种方式,它是使用 java.lang.Thread 中的方法 getContextClassLoader()setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是 AppClassLoader ,具体设置参考 sun.misc.Launcher 的构造函数。

    更多关于线程上下文加载的疑问可以参考之前我的一篇文章:理解TCCL:线程上下文类加载器

    因为线程上下文加载方式的灵活性,所以推荐在资源文件的读取时使用。

    转自 http://benjaminwhx.com/2018/07/12/%E8%AF%B4%E8%AF%B4Java%E4%B8%AD%E7%9A%84%E8%B5%84%E6%BA%90%E6%96%87%E4%BB%B6%E7%9A%84%E8%AF%BB%E5%8F%96/

  • 相关阅读:
    蓝桥杯_基础_杨辉三角
    蓝桥杯_基础_数组特征
    蓝桥杯_基础_美丽的图形
    脉象
    词根汇总
    蓝桥杯 入门训练 Fibonacci数列 解析
    制作tomcat重启.bat文件
    day23(023-递归练习)
    day27(027-反射&JDK新特性)
    day25(025-多线程(下)&GUI)
  • 原文地址:https://www.cnblogs.com/tiancai/p/9337604.html
Copyright © 2020-2023  润新知