目录

Properties 持久化资源属性

ResourceBundle 读取资源文件内容

ClassLoader 类加载器

读取路径中文乱码问题

getClass().getResourceAsStream() 获取输入流

Spring StreamUtils 直接读取资源内容到字符串

Spring ApplicationContex 访问资源

ResourceUtils 资源文件工具类

ClassPathResource 类路径资源

Hutool Setting

Spring ResourceLoader 接口访问资源文件

PathMatchingResourcePatternResolver-资源模式解析器 批量读取类路径文件资源

Spring Boot yml 文件读取方式汇总


Properties 持久化资源属性

1、Properties 类表示一组持久的属性,Properties 可以保存到流中或从流中加载,属性列表中的每个键及其对应的值都是一个字符串。

class java.util.Properties extends Hashtable<Object,Object>

2、一个属性列表可包含另一个属性列表作为它的“默认值”,如果未能在原有的属性列表中搜索到属性键,则搜索第二个属性列表。 

3、Properties 继承于 Hashtable,所以可对 Properties 对象应用 put 和 putAll 方法,但不建议使用这两个方法,因为它们允许调用者插入其键或值不是 String 的项,推荐使用 setProperty 方法。

4、Properties 类是线程安全的,多个线程可以共享一个 Properties 对象,而不需要外部同步。因为它的关键方法上加了 synchronized 关键字。

一言以蔽之:java.util.Properties 是用于快捷操作 *.properties 、*.xml 等文件工具类,可以对它们进行增删改查,持久化到磁盘。

Properties 属性增删改查

1、注意1:.properties 属性文件一行代表一条数据、结尾不能有分号、不支持中文、#开头表示注释,使用 key=value。格式如下所示:

#.properties 资源文件格式就是简单的 key=value 形式,一行写一个 key-value,换行分割,结尾不要有分号
name=root
password=123456

#资源文件中不推荐有中文,如果 key 有多个 value,可以选择使用特定的分割符连接,代码中再进行 split(分割)
color=red,yellow,blue

2、注意2:项目打包成 .jar、.war 包部署时,此时类路径文件被嵌套在 .jar、.war 包文件内部,此时资源文件内容只读,无法增删改,否则报错:java.io.FileNotFoundException: file:/x/target\x-SNAPSHOT.jar!\BOOT-INF\classes!\x.x (文件名、目录名或卷标语法不正确。)

3、Properties 读取时会自动忽略注释。

ClassPathResource 打包后_后端

在线演示源码:

ResourceBundle 读取资源文件内容

1、public abstract class java.util.ResourceBundle 主要用于读取类路径下的 *.properties 资源文件,实现语言国际化。

2、比如全世界都在用 weChat(微信),那么就需要有一个功能,对日本人显示的是日本文,对韩国人显示的是韩文,英国人显示英文,中国人显示中文,而 ResourceBundle 资源绑定就是解决这个问题,通过绑定不同的资源文件(即语言)来达到不同语言的显示。

3、ResourceBundle 直接子类:ListResourceBundle , PropertyResourceBundle。

4、单纯就读取 *.properties 资源文件内容,ResourceBundle 比 Properties 更加方便快捷,但是 Properties 功能更强大。

5、ResourceBundle 是抽象类,使用 public static final ResourceBundle getBundle(String baseName) 方法创建实例,baseName 是 classpath 下的 xxx.properties 的文件名称,不用带后缀名 ".properties"。

6、ResourceBundle 常用方法如下:

public static final void clearCache():从使用调用者的类加载器加载的缓存中删除所有资源绑定
public static final void clearCache(ClassLoader loader):从使用给定的类加载器加载的缓存中删除所有资源绑定
public boolean containsKey(String key):确定给定的 key是否包含在此 ResourceBundle或其父包中。如果 key 为 null,则抛异常。
public String getBaseBundleName():如果已知,则返回此包的基本名称,如果未知,则返回 null     
public static final ResourceBundle getBundle(String baseName):根据资源文件名(不含后缀)创建实例
public static final ResourceBundle getBundle(String baseName,Locale locale):根据资源文件名以及区域设置(Locale) 创建实例,这是做国际化的常用方法。
public abstract Enumeration<String> getKeys():返回键的枚举
public final String getString(String key):从此资源包或其父项之一获取给定 key 的值。//如果 key 不存在,则抛出异常
public final Object getObject(String key):从此资源束或其父项之一获取给定 key 的值。//如果 key 不存在,则抛出异常
@GetMapping("resourceBundle/findAllByClassPath")
    public Map<String, String> findAllByClassPath(String classPath) {
        Map<String, String> stringMap = new HashMap<>();

        classPath = StrUtil.isBlank(classPath) ? "config/config2" : classPath;
        // 如果资源文件不是在类路径根目录下,则使用 "/" 或者 "." 符号,如 data/config、data.config 都可以,但是不要再写后缀名 '.properties'
        // 如果类路径下找不到指定的属性文件,则抛出异常 MissingResourceException: Can't find bundle for base name xxx
        ResourceBundle resourceBundle = ResourceBundle.getBundle(classPath);
        // value 可以不存在,但是如果 key 为 null 或者不存在,则抛出异常。可以使用 containsKey(String key) 先判断是否 key 存在
        String name = resourceBundle.getString("name");
        String password = resourceBundle.getString("password");
        String color = resourceBundle.getString("color");
        String[] colors = resourceBundle.getString("color").split(",");
        Enumeration<String> enumeration = resourceBundle.getKeys();

        System.out.println("name=" + name + ", password" + password + ", color=" + color);
        System.out.println(Arrays.asList(colors));
        System.out.println("===============");
        while (enumeration.hasMoreElements()) {
            String elementName = enumeration.nextElement();
            String elementValue = resourceBundle.getString(elementName);
            System.out.println(elementName + "=" + elementValue);
            stringMap.put(elementName, elementValue);
        }

        // {"password":"123456","color":"red,yellow,blue","name":"root"}
        return stringMap;
    }

在线演示源码:

ClassLoader 类加载器

1、java.lang.ClassLoader 类是一个抽象类,负责加载类的对象。如果给定类的二进制名称,那么类加载器会试图查找或生成构成类定义的数据。一般策略是将名称转换为某个文件名,然后从文件系统读取该名称的“类文件”。

2、ClassLoader 类使用委托模型来搜索类和资源。每个 ClassLoader 实例都有一个相关的父类加载器。需要查找类或资源时,ClassLoader 实例会在试图亲自查找类或资源之前,将搜索类或资源的任务委托给其父类加载器。

3、 类加载器读取:读取类路径下任意位置中的任意资源,可以直接拿到文件的输入流,或者文件的绝对地址url,即可以读又可以写。

4、默认就是相对类路径下,所以路径前缀不要加 /。主要就是下面两个方法,获取到 URL或者输入流之后,就可以使用各种合适的方式读写内容了。

URL url = classLoader.getResource("data/config.properties");
InputStream inputStream = classLoader.getResourceAsStream("data/config.properties");

在线演示源码:

读取路径中文乱码问题

1、ClassLoader.getResource 方法内部使用 utf-8 对路径信息进行了编码,当路径中存在中文或空格等字符时,就会对它们进行转换,这样获取路径信息时就会出现 utf-8 编码后的字符,如 '85%ac%e5%bc%80%e9%85%8d%e7%bd' ,看起以来就像乱码一样,导致路径不存在。

2、解决办法是使用 URLDecoder.decoder 方法对地址进行解码,以得到原始路径。

getClass().getResourceAsStream() 获取输入流

/**
     * 可以直接使用 getClass().getResourceAsStream() 方法,它 会返回一个 InputStream 对象。
     * 如果此对象是由引导类加载器加载的,则该方法将委托给{@link ClassLoader#getSystemResourceAsStream}。
     * http://localhost:8080/class/getResourceAsStream
     * <p>
     * 即便是打包成 .jar、.war 包部署到生产环境,照样读取正常。
     *
     * @return
     * @throws Exception
     */
    @GetMapping("class/getResourceAsStream")
    public ResultData getResourceAsStream() throws Exception {
        List<String> readLines = new ArrayList<>();
        // 类路径下的文件时必须以"/"开头.
        String classPath = "/config/配置说明.setting";
        InputStream inputStream = getClass().getResourceAsStream(classPath);
        if (inputStream != null) {
            try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
                readLines = IoUtil.readLines(bufferedReader, readLines);
            }
        } else {
            return ResultData.error("读取类路径下文件失败:" + classPath);
        }
        return ResultData.success(readLines);
    }


Spring StreamUtils 直接读取资源内容到字符串

/**
     * Spring 框架的 StreamUtils 类提供了一个便捷的方法来直接读取资源内容到字符串
     * http://localhost:8080/streamUtils/copyToString
     * <p>
     * 即便是打包成 .jar、.war 包部署到生产环境,照样读取正常。
     *
     * @return
     * @throws Exception
     */
    @GetMapping("streamUtils/copyToString")
    public ResultData streamUtils() throws Exception {
        String classPath = "config/配置说明.setting";
        Resource resource = new ClassPathResource(classPath);
        String copyToString = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
        return ResultData.success(copyToString);
    }


Spring ApplicationContex 访问资源

/**
     * Spring 的 ApplicationContext 提供了一种访问资源的方式,它允许你按名称查找资源。
     * http://localhost:8080/applicationContext/getResource
     * <p>
     * 即便是打包成 .jar、.war 包部署到生产环境,照样读取正常。
     * <p>
     * 直接从容器中获取 ApplicationContext 实例即可,默认已经有了。
     */
    @javax.annotation.Resource
    private ApplicationContext applicationContext;

    @GetMapping("applicationContext/getResource")
    public ResultData applicationContext() throws Exception {
        List<String> readLines = new ArrayList<>();
        String classPath = "classpath:config/配置说明.setting";
        Resource resource = applicationContext.getResource(classPath);
        try (InputStream inputStream = resource.getInputStream()) {
            readLines = IoUtil.readLines(inputStream, Charset.forName("UTF-8"), readLines);
        }
        return ResultData.success(readLines);
    }


ResourceUtils 资源文件工具类

1、Spring Framework core 包下面的工具类,用于将资源位置解析为文件系统中的文件的实用程序方法。主要用于框架内部使用。

2、JAR、War 包内部的文件无法获取,必须是一个独立的文件,即项目打包后,无法再获取包中的文件。不推荐使用。


ClassPathResource 类路径资源

1、轻松读取类路径下下的任意资源文件,如果路径有前缀 / 也会自动被去掉。


@GetMapping("resource/read1")
    public ResultData resource1() throws IOException {
        Resource resource = new ClassPathResource("application.yml");
        try (InputStream inputStream = resource.getInputStream()) {
            String readUtf8 = IoUtil.readUtf8(inputStream);
            return ResultData.success(readUtf8);
        }
    }

Hutool Setting

1、Setting 除了兼容 Properties 文件格式外,还提供了一些特有功能:各种编码方式支持、支持中文、支持${key}变量等等。

2、格式与 Properties 基本一致,文件后缀名称 可以是 .setting 或者 .properties :


Spring ResourceLoader 接口访问资源文件

1、Spring 框架提供了 Resource 接口和 ResourceLoader 接口来方便地访问资源文件。

@javax.annotation.Resource
    private ResourceLoader resourceLoader;

    @GetMapping("resourceLoader/read1")
    public ResultData resourceLoader() throws Exception {
        String classPath = "classpath:config/配置说明.setting";
        Resource resource = resourceLoader.getResource(classPath);
        try (InputStream inputStream = resource.getInputStream();
             InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
             BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
            List<String> readLines = new ArrayList<>();
            readLines = IoUtil.readLines(bufferedReader, readLines);
            return ResultData.success(readLines);
        }
    }


PathMatchingResourcePatternResolver-资源模式解析器 批量读取类路径文件资源

1、使用场景1:读取服务包中指定目录下的所有文件信息,比如想读取整个服务中的所有 .html 文件信息。

2、即便是打包成 .jar、.war 包部署到生产环境,照样读取正常。

@GetMapping("classLoader/listFiles2")
    public ResultData listFiles2() throws Exception {
        List<Map<String, Object>> fileInfos = new ArrayList<>();
        // 创建 资源模式解析器 对象,ClassLoader 访问将通过线程上下文类加载器进行
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        // 将给定的位置模式解析为 Resource 资源对象(包括目录与文件)。/* 表示直接子级(不包括孙级),/** 表示递归下面的所有子孙级资源
        // 改为自己项目实际的类路径
        Resource[] resources = resolver.getResources("static/**");
        for (Resource resource : resources) {
            Map<String, Object> fileInfo = new HashMap<>(16);
            // 确定此资源的文件名,即通常是路径的最后一部分:例如“myfile.txt";如果此类资源没有文件名,则返回 null;
            fileInfo.put("fileName", resource.getFilename());
            // 返回此资源的描述;
            fileInfo.put("description", resource.getDescription());
            // 获取资源的绝对路径,并防止中文乱码
            fileInfo.put("absolutePath", URLDecoder.decode(resource.getURL().getPath(), "utf-8"));
            if (StrUtil.endWithIgnoreCase(resource.getFilename(), "html")) {
                // 如果是 html 文件,则读取它的 title 值
                String readUtf8 = IoUtil.readUtf8(resource.getInputStream());
                String title = readUtf8.substring(readUtf8.indexOf("<title>") + 7, readUtf8.indexOf("</title>"));
                // title=CSRF 跨站请求
                // title=蚩尤后裔
                System.out.println("title=" + title);
            }
            fileInfos.add(fileInfo);
        }
        return ResultData.success(fileInfos);
    }