Java中读取YML配置文件的多种实现方式

10 次阅读

YML(YAML)是一种常见的配置文件格式,因其层次结构清晰、语法简洁,被广泛应用于 Java、Spring Boot、微服务以及各种自动化项目中。实际开发时,读取 YML 配置并不只有一种方式,具体方案取决于项目是否使用 Spring Boot、配置结构是否固定,以及是否需要动态读取。

一、YML配置文件的基本结构

常见的 application.yml 配置如下:

YAML
server:
  port: 8080

database:
  url: jdbc:mysql://localhost:3306/test
  username: root
  password: 123456

app:
  name: demo
  enabled: true
  timeout: 3000

YAML通过缩进表示层级关系。例如:

YAML
database:
  url: jdbc:mysql://localhost:3306/test

对应的逻辑结构可以理解为:

database
 └── url = jdbc:mysql://localhost:3306/test

需要注意,YAML对缩进比较敏感,通常使用空格而不是Tab,并且同一层级必须保持一致。


二、使用Spring Boot读取YML配置

如果项目基于Spring Boot,通常不需要手动解析YML文件,框架已经提供了多种方便的配置绑定方式。

1. 使用@Value读取单个配置

配置文件:

YAML
app:
  name: JavaDemo
  timeout: 5000

Java代码:

Java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class AppConfig {

    @Value("${app.name}")
    private String name;

    @Value("${app.timeout}")
    private Integer timeout;

    public void printConfig() {
        System.out.println("name = " + name);
        System.out.println("timeout = " + timeout);
    }
}

运行后可以获得:

name = JavaDemo
timeout = 5000

这种方式适合读取少量配置。如果配置项比较多,大量使用 @Value 会导致代码比较分散。


三、使用@ConfigurationProperties批量绑定YML

对于一组具有相同业务含义的配置,@ConfigurationProperties通常更加合适。

例如YML:

YAML
app:
  name: JavaDemo
  version: 1.0.0
  enabled: true
  timeout: 5000

Java实体类:

Java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {

    private String name;
    private String version;
    private Boolean enabled;
    private Integer timeout;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public Boolean getEnabled() {
        return enabled;
    }

    public void setEnabled(Boolean enabled) {
        this.enabled = enabled;
    }

    public Integer getTimeout() {
        return timeout;
    }

    public void setTimeout(Integer timeout) {
        this.timeout = timeout;
    }
}

这样Spring Boot会自动将:

YAML
app:
  name: JavaDemo

绑定到:

Java
private String name;

相比逐个使用 @Value,这种方式更适合数据库、Redis、第三方服务等具有多个配置项的场景。

如果使用较新的Spring Boot版本,也可以通过构造器绑定或记录类型减少样板代码。


四、使用Map接收复杂YML配置

当配置结构不固定,或者配置项数量可能动态变化时,可以使用Map。

例如:

YAML
servers:
  server1:
    host: 192.168.1.10
    port: 8080
  server2:
    host: 192.168.1.20
    port: 8081

可以定义:

Java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
@ConfigurationProperties(prefix = "servers")
public class ServerProperties {

    private Map<String, ServerConfig> server;

    public Map<String, ServerConfig> getServer() {
        return server;
    }

    public void setServer(Map<String, ServerConfig> server) {
        this.server = server;
    }
}

配置对象:

Java
public class ServerConfig {

    private String host;
    private Integer port;

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public Integer getPort() {
        return port;
    }

    public void setPort(Integer port) {
        this.port = port;
    }
}

这种方案适合服务节点、业务规则、数据源等结构化程度较高但数量不固定的配置。


五、使用Spring Environment读取YML

如果只是需要在代码中根据配置键读取值,也可以直接使用Spring提供的 Environment。

Java
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

@Component
public class ConfigReader {

    private final Environment environment;

    public ConfigReader(Environment environment) {
        this.environment = environment;
    }

    public void read() {
        String name = environment.getProperty("app.name");
        String timeout = environment.getProperty("app.timeout");

        System.out.println(name);
        System.out.println(timeout);
    }
}

还可以直接指定类型:

Java
Integer timeout = environment.getProperty(
        "app.timeout",
        Integer.class
);

如果配置不存在,还可以提供默认值:

Java
String name = environment.getProperty(
        "app.name",
        "default-app"
);

Environment比较适合根据配置键灵活读取数据,但如果需要将一整个配置模块映射成Java对象,@ConfigurationProperties通常更加清晰。


六、使用SnakeYAML直接解析YML文件

如果项目不是Spring Boot,只使用标准Java应用,也可以通过SnakeYAML解析YML。

Maven依赖:

XML

    org.yaml
    snakeyaml
    2.x

具体版本应根据项目的Java版本和依赖管理策略选择。

准备配置文件:

YAML
app:
  name: JavaDemo
  port: 8080

Java读取代码:

Java
import org.yaml.snakeyaml.Yaml;

import java.io.InputStream;
import java.util.Map;

public class YamlReader {

    public static void main(String[] args) {
        Yaml yaml = new Yaml();

        try (InputStream inputStream =
                     YamlReader.class
                             .getClassLoader()
                             .getResourceAsStream("application.yml")) {

            if (inputStream == null) {
                throw new IllegalArgumentException(
                        "application.yml not found"
                );
            }

            Map<String, Object> data = yaml.load(inputStream);

            System.out.println(data);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

对于上述YML,解析结果通常是嵌套的Map结构,可以继续获取具体值:

Java
Map<String, Object> app =
        (Map<String, Object>) data.get("app");

String name = (String) app.get("name");
Integer port = (Integer) app.get("port");

这种方法不依赖Spring,适合普通Java程序、工具程序或者需要自行控制配置加载过程的项目。


七、将YML直接映射为Java对象

SnakeYAML除了读取Map,也可以将YAML数据映射到JavaBean。不过对于复杂配置,需要特别注意类型定义以及不同版本SnakeYAML的API差异。

一种简单的思路是先解析:

Java
Yaml yaml = new Yaml();

try (InputStream inputStream =
             getClass().getClassLoader()
                     .getResourceAsStream("application.yml")) {

    Map<String, Object> data = yaml.load(inputStream);
}

然后通过自己的转换逻辑或者JSON转换库将Map转换为Java对象。

对于Spring Boot项目,没有必要为了实现这一效果自行编写解析代码,因为 @ConfigurationProperties已经提供了成熟的配置绑定机制。


八、读取classpath目录下的YML文件

YML文件通常放在:

src/main/resources/

例如:

src
└── main
    └── resources
        ├── application.yml
        └── config.yml

编译之后,这些资源通常会进入应用的classpath。

Java中可以通过:

Java
InputStream inputStream =
        getClass().getClassLoader()
                .getResourceAsStream("config.yml");

读取。

也可以使用:

Java
InputStream inputStream =
        Thread.currentThread()
                .getContextClassLoader()
                .getResourceAsStream("config.yml");

相比直接写磁盘路径,classpath方式更加适合打包后的JAR程序。

需要注意:

Java
new File("src/main/resources/config.yml")

只适合开发阶段的某些运行环境。应用打包成JAR以后,资源文件通常不再以普通文件形式存在,因此不应该依赖项目源码目录结构。


九、读取指定磁盘位置的YML文件

如果YML是外部配置文件,例如:

/etc/myapp/application.yml

或者:

D:/config/application.yml

可以直接使用Java NIO:

Java
import org.yaml.snakeyaml.Yaml;

import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;

public class ExternalYamlReader {

    public static void main(String[] args) throws Exception {

        Path path = Path.of("D:/config/application.yml");

        try (InputStream inputStream =
                     Files.newInputStream(path)) {

            Yaml yaml = new Yaml();

            Object data = yaml.load(inputStream);

            System.out.println(data);
        }
    }
}

这种方式适用于配置文件与程序本身分离的部署模式,例如服务器部署、Docker环境以及需要独立维护配置的应用。


十、读取YML中的数组配置

YML不仅支持键值,还可以表示列表。

例如:

YAML
app:
  name: JavaDemo
  servers:
    - 192.168.1.10
    - 192.168.1.20
    - 192.168.1.30

使用Spring Boot可以定义:

Java
@ConfigurationProperties(prefix = "app")
@Component
public class AppProperties {

    private String name;

    private List<String> servers;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<String> getServers() {
        return servers;
    }

    public void setServers(List<String> servers) {
        this.servers = servers;
    }
}

如果使用SnakeYAML,则可以直接获得List:

Java
Map<String, Object> data = yaml.load(inputStream);

Map<String, Object> app =
        (Map<String, Object>) data.get("app");

List<String> servers =
        (List<String>) app.get("servers");

YAML列表和Map嵌套使用非常灵活,但手动解析时也会增加类型转换工作量。


十一、读取多环境YML配置

Spring Boot项目中经常会出现:

application.yml
application-dev.yml
application-test.yml
application-prod.yml

公共配置可以放在:

YAML
# application.yml
server:
  port: 8080

开发环境配置放在:

YAML
# application-dev.yml
app:
  name: demo-dev

生产环境则可以:

YAML
# application-prod.yml
app:
  name: demo-prod

通过Spring Boot的配置机制选择对应环境后,应用会按照配置加载规则处理不同配置文件。

较新的Spring Boot项目也可以使用配置文件中的:

YAML
spring:
  config:
    activate:
      on-profile: dev

来控制配置块适用的Profile。


十二、读取YML时常见的问题

1. 文件读取结果为null

例如:

Java
getClass().getClassLoader()
        .getResourceAsStream("config.yml");

返回 null,通常意味着文件没有位于classpath对应位置,或者文件名写错。

检查:

src/main/resources/config.yml

是否真实存在,同时确认文件名大小写。


2. YAML缩进错误

下面这种写法容易产生问题:

YAML
database:
   url: xxx
    username: root

同级属性应该保持相同缩进:

YAML
database:
  url: xxx
  username: root

YAML对缩进结构非常敏感,因此出现解析异常时,首先检查空格数量。


3. 类型转换异常

例如:

YAML
app:
  timeout: abc

Java却定义:

Java
private Integer timeout;

此时无法正常转换。

如果配置本身表示数字,就应该写成:

YAML
app:
  timeout: 5000

同时Java字段使用合适的数值类型。


4. 配置键路径写错

例如实际配置为:

YAML
database:
  username: root

读取时却写成:

Java
environment.getProperty("db.username");

自然无法得到预期结果。

对于嵌套YML配置,Spring Boot通常使用点号表示层级:

database.username

5. 把YML当成普通Properties文件处理

Properties通常写成:

properties
server.port=8080

而YML采用:

YAML
server:
  port: 8080

两者虽然都用于配置,但文件格式和解析方式不同。使用普通Properties解析器直接处理YML,通常无法得到正确结果。


十三、不同读取方式如何选择

实际开发中,可以根据项目情况进行选择。

使用场景推荐方式
Spring Boot读取单个配置@Value
Spring Boot读取一组配置@ConfigurationProperties
根据配置键动态读取Environment
普通Java项目SnakeYAML
配置结构复杂且需要手动控制SnakeYAML + Java对象
JAR内部资源ClassLoader读取
外部配置文件Files.newInputStream()
多环境配置Spring Boot Profile

对于Spring Boot应用,优先考虑配置绑定机制,而不是在业务代码中频繁手动解析YML。这样不仅代码更简洁,也更容易进行类型检查、配置维护和后期扩展。

十四、实际项目中的配置设计建议

YML配置文件适合表达层级关系,但并不意味着所有内容都应该放进一个文件。项目规模较大时,可以按照环境、业务模块和敏感信息进行合理拆分。

例如:

YAML
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/demo
    username: root

对应:

Java
@ConfigurationProperties(prefix = "spring.datasource")

可以让数据库配置集中管理。

对于密码、访问令牌等敏感信息,则不建议直接将真实值长期硬编码在代码仓库中的YML文件里。实际部署时可以结合环境变量、密钥管理服务或其他安全配置方案。

另外,配置类中的字段名称应尽量与YML结构保持一致,避免在代码中进行大量字符串拼接。对于复杂配置,使用明确的Java对象建模通常比到处调用 getProperty() 更容易维护。

Java读取YML配置的核心并不只是“把文件读出来”,而是根据应用架构选择合适的配置加载方式。Spring Boot项目可以优先使用 @Value、Environment 和 @ConfigurationProperties;独立Java程序则可以借助SnakeYAML进行解析。掌握这几种方案后,无论是简单的键值配置、嵌套对象、List集合,还是多环境配置,都可以找到对应的实现方式。