说说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/

原文地址:https://www.cnblogs.com/tiancai/p/9337604.html

时间: 2024-11-04 09:22:51

说说Java中的资源文件的读取的相关文章

Java中获取资源文件的方法总结

这里总结3中方法获取资源文件的 ServletContext Class ClassLoader 文件的位置 1. ServletContext public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter pw = response.getWriter(); ServletContext context

java的properties资源文件的读取

package com.lideng.work325; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.u

java中io创建文件和读取文件

简单了解IO流:https://www.cnblogs.com/weibanggang/p/10034325.html package com.wbg.iodemo1128; import java.io.*; public class OutputStreamDemo { public static void main(String[] args) throws IOException { reader(); } //输入字节流inputStream static void inputStre

ServletContext读取Web应用中的资源文件

1 package cn.itcast; 2 3 import java.io.FileInputStream; 4 import java.io.IOException; 5 import java.io.InputStream; 6 import java.io.PrintWriter; 7 import java.util.Properties; 8 9 import javax.servlet.ServletContext; 10 import javax.servlet.Servlet

java 资源文件的读取

Java将配置文件当作一种资源(resource)来处理,并且提供了两个类来读取这些资源,一个是Class类,另一个是ClassLoader类. gradle 项目 项目目录结构  用Class类加载资源文件 public InputStream getResourceAsStream(String name) 查找具有给定名称的资源.查找与给定类相关的资源的规则是通过定义类的 class loader 实现的.此方法委托此对象的类加载器.如果此对象通过引导类加载器加载,则此方法将委托给 Cla

java中IO写文件工具类

下面是一些根据常用java类进行组装的对文件进行操作的类,平时,我更喜欢使用Jodd.io中提供的一些对文件的操作类,里面的方法写的简单易懂. 其中jodd中提供的JavaUtil类中提供的方法足够我们使用,里面的方法写的非常简练,例如append,read等方法,封装更好,更符合面向对象, 这里面我写的一些方法可多都是模仿jodd,从里面进行抽取出来的. /** * 获取路径文件夹下的所有文件 * @param path * @return */ public static File[] ge

文件操作 - Windows资源文件的读取

=============== Windows资源文件的读取 =============== Windows资源操作函数 12 LoadAccelerators 加载快捷键资源 14 LoadBitmap 加载位图资源 16 LoadCursor 加载光标资源 18 LoadIcon 加载图标资源 20 LoadMenu 加载菜单资源 22 LoadString 加载字符串资源 Windows资源操作函数 29 FindResource 从指定模块中加载指定名称.类型的资源 31 SizeofR

maven 编译部署src/main/java下的资源文件

maven 编译部署src/main/java下的资源文件 maven默认会把src/main/resources下的所有配置文件以及src/main/java下的所有java文件打包或发布到target\classes下面, 但是现实我们可能会在src/main/java下面也放置一些配置文件如hibernate配置文件或mybatis mapper配置文件等, 如果不做一些额外配置,那我们打包后的项目可能找不到这些必须的资源文件,因此在pom.xml中增加类似如下配置: <build> &

springboot jar包运行中获取资源文件

1. 今天晚上写了一个程序,基于Spring boot的一个小网站,发现使用FileUtils.class.getResource(path)来获取jar包中的资源文件并不能成功,其路径很奇怪 file:/Users/lonecloud/Documents/ideaCode/git/export/target/export-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/temp/temp.xls 查阅资料后,并且查看jar包中的资源文件发现有!还有classes!这