ZetCode

在 Spring Boot 中加载资源

最后修改于 2023 年 7 月 29 日

在本文中,我们将展示如何在 Spring Boot 应用程序中加载资源。

Spring 是一个流行的 Java 应用程序框架,用于创建企业应用程序。Spring Boot 是一种以最小的努力创建独立、生产级别的 Spring 应用程序的方式。

Spring Boot 资源

资源是数据,例如图像、音频和文本,程序需要以独立于程序代码位置的方式访问这些数据。

由于 java.net.URL 无法满足处理所有类型的底层资源的需求,Spring 引入了 org.springframework.core.io.Resource。要访问资源,我们可以使用 @Value 注解或 ResourceLoader 类。

Spring Boot 加载资源示例

我们的应用程序是一个 Spring Boot 命令行应用程序,它计算文本文件中单词的出现次数。该文件位于 src/main/resources 目录中。

build.gradle 
...
src
├───main
│   ├───java
│   │   └───com
│   │       └───zetcode
│   │           │   Application.java
│   │           │   MyRunner.java
│   │           └───service
│   │                   CountWords.java
│   └───resources
│           application.yml
│           thermopylae.txt
└───test
    └───java

这是项目结构。

build.gradle
plugins {
    id 'org.springframework.boot' version '3.1.1'
    id 'io.spring.dependency-management' version '1.1.0'
    id 'java'
}

group = 'com.zetcode'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
}

这是 Gradle 构建文件。 Spring Boot 启动器是一组方便的依赖描述符,我们可以将其包含在我们的应用程序中。spring-boot-starter 依赖项是一个核心启动器,其中包括自动配置支持、日志记录和 YAML。

resources/application.yml
spring:
    main:
        banner-mode: "off"

logging:
    level:
        org:
            springframework: ERROR
        com:
            zetcode: INFO

application.yml 文件包含 Spring Boot 应用程序的各种配置设置。我们有一个 banner-mode 属性,我们可以在其中关闭 Spring 标语。此外,我们将 spring framework 的日志级别设置为 ERROR,并将我们的应用程序设置为 INFO。该文件位于 src/main/resources 目录中。

resources/thermopylae.txt
The Battle of Thermopylae was fought between an alliance of Greek city-states,
led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the
course of three days, during the second Persian invasion of Greece.
It took place simultaneously with the naval battle at Artemisium, in August
or September 480 BC, at the narrow coastal pass of Thermopylae.
The Persian invasion was a delayed response to the defeat of the first Persian
invasion of Greece, which had been ended by the Athenian victory at the Battle
of Marathon in 490 BC. Xerxes had amassed a huge army and navy, and set out to
conquer all of Greece.

这是我们在应用程序中读取的文本文件。它也位于 src/main/resources 目录中。

com/zetcode/service/CountWords.java
package com.zetcode.service;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

@Component
public class CountWords {

    public Map<String, Integer> getWordsCount(Resource res) throws IOException {

        Map<String, Integer> wordCount = new HashMap<>();

        List<String> lines = Files.readAllLines(Paths.get(res.getURI()),
                StandardCharsets.UTF_8);

        for (String line : lines) {

            String[] words = line.split("\\s+");

            for (String word : words) {

                if (word.endsWith(".") || word.endsWith(",")) {

                    word = word.substring(0, word.length() - 1);
                }

                if (wordCount.containsKey(word)) {

                    wordCount.put(word, wordCount.get(word) + 1);

                } else {

                    wordCount.put(word, 1);
                }
            }
        }

        return wordCount;
    }
}

CountWords 是一个 Spring 托管 bean,它执行对给定文件中单词的计数。文本从文件读取到句子列表中。句子被分成单词并计数。

Map<String, Integer> wordCount = new HashMap<>();

wordCount 是一个 map,其中键是单词,频率是整数。

List<String> lines = Files.readAllLines(Paths.get(res.getURI()),
        StandardCharsets.UTF_8);

我们使用 Files.readAllLines 方法一次性读取所有内容。Files.readAllLines 方法返回一个字符串列表。

for (String line : lines) {

    String[] words = line.split(" ");
...

我们遍历这些行并将它们拆分成单词;单词之间用空格分隔。

if (word.endsWith(".") || word.endsWith(",")) {

    word = word.substring(0, word.length()-1);
}

我们删除尾随的点和逗号。

if (wordCount.containsKey(word)) {

    wordCount.put(word, wordCount.get(word) + 1);

} else {

    wordCount.put(word, 1);
}

如果该单词已存在于 map 中,我们增加其频率;否则,我们将其插入到 map 中并将其频率设置为 1。

com/zetcode/MyRunner.java
package com.zetcode;

import com.zetcode.service.CountWords;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

@Component
public class MyRunner implements CommandLineRunner {

    @Value("classpath:thermopylae.txt")
    private Resource res;

    private final CountWords countWords;

    @Autowired
    public MyRunner(CountWords countWords) {
        this.countWords = countWords;
    }

    @Override
    public void run(String... args) throws Exception {

        Map<String, Integer> words =  countWords.getWordsCount(res);

        for (String key : words.keySet()) {

            System.out.println(key + ": " + words.get(key));
        }
    }
}

使用 CommandLineRunner,Spring Boot 应用程序在终端上运行。

@Value("classpath:thermopylae.txt")
private Resource res;

使用 @Value 注解,我们将文件设置为资源。

private final CountWords countWords;

@Autowired
public MyRunner(CountWords countWords) {
    this.countWords = countWords;
}

我们注入 CountWords bean。

Map<String, Integer> words =  countWords.getWordsCount(res);

for (String key : words.keySet()) {

    System.out.println(key + ": " + words.get(key));
}

我们调用 getWordsCount 方法,并接收一个单词及其频率的 map。我们遍历 map,并将键/值对打印到控制台。

com/zetcode/Application.java
package com.zetcode;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Application 设置了 Spring Boot 应用程序。@SpringBootApplication 启用了自动配置和组件扫描。

使用 ResourceLoader

之前,我们使用 @Value 注解来加载资源。以下是使用 ResourceLoader 的替代解决方案。

com/zetcode/MyRunner.java
package com.zetcode;

import com.zetcode.service.CountWords;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
public class MyRunner implements CommandLineRunner {

    private final ResourceLoader resourceLoader;
    private final CountWords countWords;

    @Autowired
    public MyRunner(ResourceLoader resourceLoader, CountWords countWords) {
        this.resourceLoader = resourceLoader;
        this.countWords = countWords;
    }

    @Override
    public void run(String... args) throws Exception {

        Resource res = resourceLoader.getResource("classpath:thermopylae.txt");

        Map<String, Integer> words =  countWords.getWordsCount(res);

        for (String key : words.keySet()) {

            System.out.println(key + ": " + words.get(key));
        }
    }
}

或者,我们可以使用 ResourceLoader 来加载资源。

private final ResourceLoader resourceLoader;
private final CountWords countWords;

@Autowired
public MyRunner(ResourceLoader resourceLoader, CountWords countWords) {
    this.resourceLoader = resourceLoader;
    this.countWords = countWords;
}

ResourceLoaderCountWords 被注入。

Resource res = resourceLoader.getResource("classpath:thermopylae.txt");

Resource 从资源加载器中通过 getResource 方法获得。

运行应用程序

应用程序在命令行上运行。

$ ./gradlew bootRun -q
...
been: 1
Athenian: 1
alliance: 1
navy: 1
fought: 1
led: 1
delayed: 1
had: 2
during: 1
three: 1
second: 1
Greece: 3
...

我们运行应用程序。-q 选项抑制 Gradle 日志。

在本文中,我们使用了 Spring Boot 应用程序中的资源。我们使用 @ValueResourceLoader 来加载资源文件。

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。迄今为止,我撰写了 1,400 多篇文章和 8 本电子书。我拥有超过十年的编程教学经验。

列出 所有 Spring Boot 教程