SpringBoot外部化配置


https://docs.spring.io/spring-boot/docs/2.1.11.RELEASE/reference/html/boot-features-external-config.html

一、配置加载顺序

Spring Boot 允许您外部化您的配置,以便您可以在不同的环境中使用相同的应用程序代码。您可以使用属性文件、YAML文件、环境变量和命令行参数来具体化配置。属性值可以通过使用 @Value 注释直接注入到bean中,通过Spring的环境抽象进行访问,或者通过 @ConfigurationProperties 绑定到结构化对象。

Spring Boot 使用一个非常特殊的 PropertySource 顺序,旨在允许合理的值重写。属性按以下顺序考虑:

1、Spring Boot DevTools 全局设置属性文件位于你的主目录下(在DevTools处于活动状态时,是~/.spring-boot-devtools.properties)。这个文件允许你配置一些DevTools的行为。

2、@TestPropertySource 是 Spring Boot 中用于为测试提供配置属性的注解。

3、在Spring Boot中,@SpringBootTest注解和相关的测试注解配置的属性

4、命令行参数。(Command line arguments.)

5、来自 SPRING_APPLICATION_JSON 的属性(嵌入在环境变量或系统属性中的内联JSON)。

6、ServletConfig初始化参数。

7、ServletContext初始化参数。

8、JNDI attributes from java:comp/env.

9、java系统属性 Java System properties (System.getProperties()).

10、操作系统环境变量

11、A RandomValuePropertySource that has properties only in random.*.

12、jar包外部(和jar包同级目录)的配置文件 application-{profile}

13、jar包内部的配置文件 application-{profile}

14、jar包外部的配置文件 application.properties

15、jar包内部的配置文件 application.properties

16、@PropertySource@Configuration类上。注意,这些属性源直到应用上下文刷新时才会被添加到环境中。这意味着,对于一些在应用刷新开始之前就需要读取的属性(例如 logging.*spring.main.*),这种配置方式可能太晚了。

17、SpringApplication.setDefaultProperties 设置应用程序默认的属性

1、DevTools

Spring Boot DevTools 全局设置属性文件位于你的主目录下(在DevTools处于活动状态时,是~/.spring-boot-devtools.properties)。这个文件允许你配置一些DevTools的行为。

如果你想修改这些设置,可以按照以下步骤操作:

  1. 打开你的主目录(在Linux和Mac上,这通常是~目录,在Windows上,这通常是C:\Users\YourUserName)。
  2. 寻找一个名为 .spring-boot-devtools.properties 的文件。如果文件不存在,你需要创建它。
  3. 打开这个文件,并添加你想要的设置。例如,你可以添加以下设置来更改DevTools的默认端口:
spring.devtools.remote.port=8081

保存并关闭文件。然后重启你的应用,新的设置就会生效。

Spring Boot 的早期版本中,DevTools的全局设置通常是在 application.propertiesapplication.yml 文件中设置的。然而,从Spring Boot 2.0开始,这些设置被移到了一个新的文件中,即 .spring-boot-devtools.properties

需要注意的是,虽然你可以在 .spring-boot-devtools.properties 文件中设置很多DevTools的属性,但并不是所有的属性都可以在这里设置。一些特定的属性(例如 spring.devtools.remote.secret )只能在你的应用的 application.propertiesapplication.yml 文件中设置。

2、@TestPropertySource

@TestPropertySource 是 Spring Boot 中用于为测试提供配置属性的注解。它可以被用于单个测试类或者测试方法上,为特定的测试提供自定义的配置属性。

使用方法很简单,只需要在测试类或者测试方法上加上 @TestPropertySource 注解,然后在注解中提供配置文件路径或者直接提供属性。

例如,如果你想在单个测试类中使用自定义配置,可以这样做:

@RunWith(SpringRunner.class)  
@TestPropertySource(locations = "classpath:test.properties")  
public class MyTestClass {  
    // your test methods here  
}

或者,你也可以直接在注解中提供属性:

@RunWith(SpringRunner.class)  
@TestPropertySource(properties = { "my.property=myValue" })  
public class MyTestClass {  
    // your test methods here  
}

如果你想在单个测试方法中使用自定义配置,可以这样做:

@RunWith(SpringRunner.class)  
public class MyTestClass {  
    @TestPropertySource(locations = "classpath:test.properties")  
    @Test  
    public void myTestMethod() {  
        // your test code here  
    }  
}

这样,你就可以为特定的测试提供自定义的配置属性了。

3、@SpringBootTest

在Spring Boot中,@SpringBootTest注解和相关的测试注解(如@IntegrationTest@WebTest等)具有一个properties属性,它允许您在测试中注入自定义属性。这对于在测试环境中配置和定制应用程序的行为非常有用。

例如,假设您希望在测试环境中使用一个不同的数据库URL。您可以在@SpringBootTest注解上设置properties属性,如下所示:

@SpringBootTest(properties = {  
    "spring.datasource.url=jdbc:h2:mem:testdb",  
    "spring.datasource.driverClassName=org.h2.Driver",  
    "spring.datasource.username=sa",  
    "spring.datasource.password="  
})  
public class MyTestClass {  
    // your tests here  
}

在上面的示例中,我们使用H2内存数据库作为测试数据库。这只是一个例子,您可以根据需要设置任何其他属性。

此外,如果您需要为特定的测试类或测试方法设置自定义属性,您可以使用@TestPropertySource注解。例如:

@RunWith(SpringRunner.class)  
@TestPropertySource(locations = "classpath:test.properties")  
public class MyTestClass {  
    // your tests here  
}

在上面的示例中,我们从类路径中的test.properties文件中读取测试属性。

4、命令行参数

java -jar target/service-a-0.0.1-SNAPSHOT.jar --spring.cloud.nacos.config.server-addr=127.0.0.3:8848

5、SPRING_APPLICATION_JSON

您提到的 SPRING_APPLICATION_JSON 是一个环境变量或系统属性,其中包含嵌入的 JSON 数据。Spring Boot 应用程序可以读取此变量中的属性,并将其用作配置值。

如果您想从 SPRING_APPLICATION_JSON 中获取属性,您可以使用 @Value 注解将其注入到您的 Spring Bean 中。以下是一个示例:

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

@Component  
public class MyComponent {  
    @Value("${myProperty}")  
    private String myProperty;  

    // Getter and setter methods for myProperty  
}

在上面的示例中,myProperty 是从 SPRING_APPLICATION_JSON 中提取的一个属性。通过使用 @Value 注解,您可以将该属性的值注入到 myProperty 字段中。

请注意,您需要确保 SPRING_APPLICATION_JSON 环境变量或系统属性已正确设置,并且包含包含所需属性的有效 JSON 数据。例如:

export SPRING_APPLICATION_JSON='{"myProperty":"myValue"}'

通过将上述命令运行在您的终端中,您可以将包含 {"myProperty":"myValue"}SPRING_APPLICATION_JSON 环境变量设置为 myValue。然后,在您的 Spring Boot 应用程序中,您可以使用上述示例中的方法来读取该值。

可以在命令行中使用环境变量提供SPRING_APPLICATION_JSON属性。例如,您可以在UNIX shell中使用以下行:

SPRING_APPLICATION_JSON='{"acme":{"name":"test"}}' java -jar myapp.jar

java -Dspring.application.json='{"name":"test"}' -jar myapp.jar

java -jar myapp.jar --spring.application.json='{"name":"test"}'

6、ServletConfig初始化参数

ServletConfig 对象是在 servlet 类初始化时创建的,它提供了servlet的初始化参数信息。ServletConfig对象可以通过 servlet 的 getServletConfig() 方法获取。

ServletConfig对象提供了以下几种方法来获取servlet的初始化参数:

  1. getInitParameter(String name):获取指定名称的初始化参数的值。
  2. getInitParameterNames():获取所有初始化参数名称的枚举集合。
  3. getServletContext():获取ServletContext对象,该对象是在servlet被添加到servlet容器时创建的。

在使用ServletConfig对象时,我们需要在 web.xml 文件中为servlet指定初始化参数。例如:

<servlet>  
    <servlet-name>MyServlet</servlet-name>  
    <servlet-class>com.example.MyServlet</servlet-class>  
    <init-param>  
        <param-name>myParam</param-name>  
        <param-value>myValue</param-value>  
    </init-param>  
</servlet>

在上面的例子中,我们为MyServlet指定了一个名为myParam的初始化参数,其值为myValue。在MyServlet中,我们可以使用ServletConfig对象来获取这个初始化参数的值:

public class MyServlet extends HttpServlet {  
    private String myParam;  

    public void init(ServletConfig config) throws ServletException {  
        super.init(config);  
        myParam = config.getInitParameter("myParam");  
    }  
}

在上面的代码中,我们在MyServlet的init方法中获取了名为myParam的初始化参数的值,并将其存储在了myParam变量中。

7、ServletContext初始化参数

ServletContext 的初始化参数是通过在 web.xml 文件中配置 <context-param> 元素来设置的。这些参数在整个Web应用程序的生命周期中都是可访问的。您可以在应用程序的任何servlet或JSP页面中通过ServletContext对象访问这些参数。

以下是如何设置和使用 ServletContext 初始化参数的步骤:

  1. web.xml 文件中,添加 <context-param> 元素以定义初始化参数。语法如下:
<context-param>  
    <param-name>参数名称</param-name>  
    <param-value>参数值</param-value>  
</context-param>

例如:

<context-param>  
    <param-name>databaseUrl</param-name>  
    <param-value>jdbc:mysql://localhost:3306/mydb</param-value>  
</context-param>

通过 ServletContext 对象访问初始化参数。您可以在servlet或JSP页面中使用以下代码片段获取ServletContext 对象,然后使用 getParameter() 方法获取初始化参数的值:

ServletContext context = getServletContext();  
String databaseUrl = context.getParameter("databaseUrl");

注意:ServletContext对象在servlet的整个生命周期中都是可访问的,因此您可以将其存储在servlet的实例变量中,以便在多个请求之间共享数据。

8、JNDI

JNDI (Java Naming and Directory Interface) 是 Java 的一部分,它提供了查找和访问由名称命名的对象、服务和服务器的方法。java:comp/env 是 JNDI 中的一个名称空间,它允许应用程序在它的运行环境中查找和访问环境属性。

java:comp/env 中,你可以设置和使用各种类型的属性,包括但不限于字符串、整数、布尔值,甚至其他对象。这些属性可以被应用程序用来获取配置信息,例如数据库连接信息,服务端点等。

在 Java 应用程序中,你可以使用 InitialContext 来访问 java:comp/env 命名空间。以下是一个示例:

import javax.naming.*;  
import javax.naming.directory.*;  

public class JndiExample {  
    public static void main(String[] args) {  
        try {  
            Context initCtx = new InitialContext();  
            Context envCtx = (Context) initCtx.lookup("java:comp/env");  

            // 获取名为 "databaseUrl" 的属性  
            String databaseUrl = (String) envCtx.lookup("databaseUrl");  
            System.out.println("Database URL: " + databaseUrl);  
        } catch (NamingException e) {  
            e.printStackTrace();  
        }  
    }  
}

在这个例子中,我们首先创建了一个 InitialContext 对象,然后使用它来查找 java:comp/env 上下文。然后,我们在这个上下文中查找名为 “databaseUrl” 的属性,并将其打印出来。在实际的应用程序中,这个 “databaseUrl” 属性可能会被设置在部署描述符(如 web.xml)中,或者通过其他方式在运行时环境中配置。

需要注意的是,JNDI 和 java:comp/env 通常用于服务器端的环境配置,因此它们可能不会在所有的 Java 应用程序中都使用到。在客户端应用程序或独立的应用程序中,你可能会使用其他的方式来获取和设置配置信息。

9、java系统属性

System.getProperties() 是 Java 中的一个方法,它返回一个包含系统属性的 Properties 对象。这些系统属性通常是由系统环境变量和 JVM 参数提供的。

这些属性可以使用 getProperty(key) 方法以键值对的形式获取。例如,你可以使用 System.getProperty("os.name") 来获取操作系统的名称。

以下是一个简单的例子:

import java.util.Properties;  

public class Main {  
    public static void main(String[] args) {  
        Properties properties = System.getProperties();  
        for (String key : properties.stringPropertyNames()) {  
            String value = properties.getProperty(key);  
            System.out.println(key + " = " + value);  
        }  
    }  
}

这个程序将打印出所有的系统属性及其对应的值。

注意,System.getProperties() 返回的 Properties 对象是不可修改的,尝试对其进行修改将抛出 UnsupportedOperationException。如果你需要修改系统属性,可以使用 System.setProperty(key, value) 方法。这将把一个属性设置为指定的值,如果这个属性之前不存在,那么它将添加到系统属性中。

10、操作系统环境变量

11、RandomValuePropertySource

RandomValuePropertySource 是 Spring Boot 中一个用于提供随机值的属性源。它的属性都以 random.* 的形式命名。

“RandomValuePropertySource”对于注入随机值很有用(例如,在秘密或测试用例中)。它可以生成整数、长整数、uuid或字符串,示例如下:

my.secret=${random.value}
my.number=${random.int}
my.bignumber=${random.long}
my.uuid=${random.uuid}
my.number.less.than.ten=${random.int(10)}
my.number.in.range=${random.int[1024,65536]}

The random.int* syntax is OPEN value (,max) CLOSE where the OPEN,CLOSE are any character and value,max are integers. If max is provided, then value is the minimum value and max is the maximum value (exclusive).

16、@PropertySource

如果你正在使用Spring Boot,那么你可以在应用的启动类上使用 @PropertySource 注解。这会使得这些属性在应用启动时即可读取。例如:

@SpringBootApplication  
@PropertySource("classpath:application.properties")  
public class MyApplication {  
    public static void main(String[] args) {  
        SpringApplication.run(MyApplication.class, args);  
    }  
}

17、setDefaultProperties

SpringApplication.setDefaultProperties 是 Spring Boot 的一个方法,允许你设置应用程序默认的属性。这通常在创建 Spring Boot 应用程序时完成。

例如,假设你有一个名为 MyApp 的 Spring Boot 应用程序,并且你想设置一个默认的属性,你可以这样做:

public static void main(String[] args) {  
    SpringApplication app = new SpringApplication(MyApp.class);  
    app.setDefaultProperties(Collections.singletonMap("my.property", "my-value"));  
    app.run(args);  
}

在这个例子中,我们通过 setDefaultProperties 方法设置了一个名为 my.property 的属性,其值为 my-value。这个值将在应用程序的配置文件中被寻找,如果该配置文件没有为这个属性提供值,那么将使用我们设定的默认值。

此外,如果你使用 Spring Boot 的自动配置功能,那么你也可以在 application.propertiesapplication.yml 文件中设置默认的属性。例如:

application.properties 文件中:

my.property=my-value

或者在 application.yml 文件中:

my:  
  property: my-value

在这种情况下,如果 application.propertiesapplication.yml 文件中的 my.property 属性没有设置值,那么将使用我们在 application.propertiesapplication.yml 文件中设定的默认值。如果这个属性在两者中都未被设置,那么将使用我们在 SpringApplication.setDefaultProperties 中设定的默认值。

二、java -jar后面的–和-D区别

在Java命令行中,-jar参数用于指定要运行的JAR文件。而在JAR文件参数后面的---D是两个不同的选项,它们具有不同的含义和用途。

  1. --(双连字符):
    • --是一个特殊的选项,用于指示JAR文件后面的参数不是JAR文件的选项,而是传递给JAR文件中的主类(Main class)的参数。在运行JAR文件时,Java虚拟机(JVM)会将--之前的所有参数作为JAR文件的选项,而将--之后的所有参数作为传递给主类的参数。
    • 例如,假设有一个名为myapp.jar的JAR文件,其中包含一个主类com.example.MyApp。要运行这个JAR文件并将一些参数传递给主类,可以使用以下命令:
java -jar myapp.jar --arg1 value1 --arg2 value2

在上述命令中,--arg1 value1--arg2 value2是传递给主类的参数。

  1. -D(单个字母D):

-D选项用于设置Java系统属性(System properties)。在Java中,可以使用System.getProperty()方法获取系统属性的值。通过使用-D选项,可以在运行JAR文件时设置系统属性,并在程序中使用这些属性。

  • 例如,假设要在运行JAR文件时设置一个名为myprop的系统属性,并将其值设置为myvalue,可以使用以下命令:
java -Dmyprop=myvalue -jar myapp.jar

在上述命令中,-Dmyprop=myvalue设置了名为myprop的系统属性,其值为myvalue。在程序中可以通过System.getProperty("myprop")获取该属性的值。

总结:

  • --用于指示JAR文件后面的参数是传递给主类的参数。
  • -D用于设置Java系统属性。

三、读取yml文件

1、Environment

在Spring中有一个类Environment,它可以被认为是当前应用程序正在运行的环境,它继承了PropertyResolver接口,因此可以作为一个属性解析器使用。先创建一个yml文件,属性如下:

person:
  name: hydra
  gender: male
  age: 18

使用起来也非常简单,直接使用@Autowired就可以注入到要使用的类中,然后调用它的getProperty()方法就可以根据属性名称取出对应的值了。

@RestController
public class EnvironmentController {
    @Autowired
    private Environment environment;

    @GetMapping("envTest")
    private void getEnv(){
        System.out.println(environment.getProperty("person.name"));
        System.out.println(environment.getProperty("person.gender"));

        Integer autoClose = environment.getProperty("person.age", Integer.class);
        System.out.println(autoClose);

        String defaultValue = environment
            .getProperty("person.other", String.class, "defaultValue");
        System.out.println(defaultValue);
    }
}

在上面的例子中可以看到,除了简单的获取外,Environment提供的方法还可以对取出的属性值进行类型转换、以及默认值的设置

除了获取属性外,还可以用来判断激活的配置文件,我们先在application.yml中激活pro文件:

spring:
  profiles:
    active: pro

可以通过acceptsProfiles方法来检测某一个配置文件是否被激活加载,或者通过getActiveProfiles方法拿到所有被激活的配置文件。测试接口:

@GetMapping("getActiveEnv")
private void getActiveEnv(){
    System.out.println(environment.acceptsProfiles("pro"));
    System.out.println(environment.acceptsProfiles("dev"));

    String[] activeProfiles = environment.getActiveProfiles();
    for (String activeProfile : activeProfiles) {
        System.out.println(activeProfile);
    }
}

打印结果:

true
false
pro

2、YamlPropertiesFactoryBean

在Spring中还可以使用YamlPropertiesFactoryBean来读取自定义配置的yml文件,而不用再被拘束于application.yml及其激活的其他配置文件。

在使用过程中,只需要通过setResources()方法设置自定义yml配置文件的存储路径,再通过getObject()方法获取Properties对象,后续就可以通过它获取具体的属性,下面看一个例子:

@GetMapping("fcTest")
public void ymlProFctest(){
    YamlPropertiesFactoryBean yamlProFb = new YamlPropertiesFactoryBean();
    yamlProFb.setResources(new ClassPathResource("application2.yml"));
    Properties properties = yamlProFb.getObject();
    System.out.println(properties.get("person2.name"));
    System.out.println(properties.get("person2.gender"));
    System.out.println(properties.toString());
}

查看运行结果,可以读取指定的application2.yml的内容:

susan
female
{person2.age=18, person2.gender=female, person2.name=susan}

但是这样的使用中有一个问题,那就是只有在这个接口的请求中能够取到这个属性的值,如果再写一个接口,不使用YamlPropertiesFactoryBean读取配置文件,即使之前的方法已经读取过这个yml文件一次了,第二个接口取到的仍然还是空值。来对这个过程进行一下测试:

@Value("${person2.name:null}")
private String name;
@Value("${person2.gender:null}")
private String gender;

@GetMapping("fcTest2")
public void ymlProFctest2(){
    System.out.println(name);
    System.out.println(gender);
}

先调用一次fcTest接口,再调用fcTest2接口时会打印null值:

null
null

想要解决这个问题也很简单,可以配合PropertySourcesPlaceholderConfigurer使用,它实现了BeanFactoryPostProcessor接口,也就是一个bean工厂后置处理器的实现,可以将配置文件的属性值加载到一个Properties文件中。使用方法如下:

@Configuration
public class PropertyConfig {
    @Bean
    public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
        PropertySourcesPlaceholderConfigurer configurer 
            = new PropertySourcesPlaceholderConfigurer();
        YamlPropertiesFactoryBean yamlProFb 
            = new YamlPropertiesFactoryBean();
        yamlProFb.setResources(new ClassPathResource("application2.yml"));
        configurer.setProperties(yamlProFb.getObject());
        return configurer;
    }
}

再次调用之前的接口,结果如下,可以正常的取到application2.yml中的属性:

susan
female

除了使用YamlPropertiesFactoryBean将yml解析成Properties外,其实我们还可以使用YamlMapFactoryBean解析yml成为Map,使用方法非常类似:

@GetMapping("fcMapTest")
public void ymlMapFctest(){
    YamlMapFactoryBean yamlMapFb = new YamlMapFactoryBean();
    yamlMapFb.setResources(new ClassPathResource("application2.yml"));
    Map<String, Object> map = yamlMapFb.getObject();
    System.out.println(map);
}

打印结果:

{person2={name=susan, gender=female, age=18}}

3、监听事件

SpringBoot是通过监听事件的方式来加载和解析的yml文件,那么我们也可以仿照这个模式,来加载自定义的配置文件。

首先,定义一个类实现ApplicationListener接口,监听的事件类型为ApplicationEnvironmentPreparedEvent,并在构造方法中传入要解析的yml文件名:

public class YmlListener implements 
    ApplicationListener<ApplicationEnvironmentPreparedEvent> {
    private String ymlFilePath;
    public YmlListener(String ymlFilePath){
        this.ymlFilePath = ymlFilePath;
    }
    //...
}

自定义的监听器中需要实现接口的onApplicationEvent()方法,当监听到ApplicationEnvironmentPreparedEvent事件时会被触发:

@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
    ConfigurableEnvironment environment = event.getEnvironment();
    ResourceLoader loader = new DefaultResourceLoader();
    YamlPropertySourceLoader ymlLoader = new YamlPropertySourceLoader();
    try {
        List<PropertySource<?>> sourceList = ymlLoader
            .load(ymlFilePath, loader.getResource(ymlFilePath));
        for (PropertySource<?> propertySource : sourceList) {
            environment.getPropertySources().addLast(propertySource);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

上面的代码中,主要实现了:

  • 获取当前环境Environment,当ApplicationEnvironmentPreparedEvent事件被触发时,已经完成了Environment的装载,并且能够通过event事件获取
  • 通过YamlPropertySourceLoader加载、解析配置文件
  • 将解析完成后的OriginTrackedMapPropertySource添加到Environment

修改启动类,在启动类中加入这个监听器:

public static void main(String[] args) {
    SpringApplication application = new SpringApplication(MyApplication.class);
    application.addListeners(new YmlListener("classpath:/application2.yml"));
    application.run(args);
}

4、SnakeYml

前面介绍的几种方式,在Spring环境下无需引入其他依赖就可以完成的,接下来要介绍的SnakeYml在使用前需要引入依赖,但是同时也可以脱离Spring环境单独使用。先引入依赖坐标:

<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.23</version>
</dependency>

准备一个yml配置文件:

person1:
  name: hydra
  gender: male
person2:
  name: susan
  gender: female

在使用SnakeYml解析yml时,最常使用的就是loadloadlAllloadAs方法,这三个方法可以加载yml文件或字符串,最后返回解析后的对象。我们先从基础的load方法开始演示:

public void test1(){
    Yaml yaml=new Yaml();
    Map<String, Object> map =
            yaml.load(getClass().getClassLoader()
                    .getResourceAsStream("snake1.yml"));
    System.out.println(map);
}

运行上面的代码,打印Map中的内容:

{person1={name=hydra, gender=male}, person2={name=susan, gender=female}}

接下来看一下loadAll方法,它可以用来加载yml中使用---连接符连接的多个文档,将上面的yml文件进行修改:

person1:
  name: hydra
  gender: male
---
person2:
  name: susan
  gender: female

在添加了连接符后,尝试再使用load方法进行解析,报错如下显示发现了另一段yml文档从而无法正常解析

这时候修改上面的代码,使用loadAll方法:

public void test2(){
    Yaml yaml=new Yaml();
    Iterable<Object> objects = 
        yaml.loadAll(getClass().getClassLoader()
            .getResourceAsStream("snake2.yml"));
    for (Object object : objects) {
        System.out.println(object);
    }
}

执行结果如下:

{person1={name=hydra, gender=male}}
{person2={name=susan, gender=female}}

可以看到,loadAll方法返回的是一个对象的迭代,里面的每个对象对应yml中的一段文档,修改后的yml文件就被解析成了两个独立的Map。

接下来再来看一下loadAs方法,它可以在yml解析过程中指定类型,直接封装成一个对象。我们直接复用上面的snake1.yml,在解析前先创建两个实体类对象用于接收:

@Data
public class Person {
    SinglePerson person1;
    SinglePerson person2;
}

@Data
public class SinglePerson {
    String name;
    String gender;
}

下面使用loadAs方法加载yml,注意方法的第二个参数,就是用于封装yml的实体类型。

public void test3(){
    Yaml yaml=new Yaml();
    Person person = 
        yaml.loadAs(getClass().getClassLoader().
            getResourceAsStream("snake1.yml"), Person.class);
    System.out.println(person.toString());
}

查看执行结果:

Person(person1=SinglePerson(name=hydra, gender=male), person2=SinglePerson(name=susan, gender=female))

实际上,如果想要将yml封装成实体对象,也可以使用另一种方法。在创建Yaml对象的时候,传入一个指定实体类的构造器对象,然后直接调用load方法就可以实现:

public void test4(){
    Yaml yaml=new Yaml(new Constructor(Person.class));
    Person person = yaml.load(getClass().getClassLoader().
            getResourceAsStream("snake1.yml"));
    System.out.println(person.toString());
}

执行结果与上面相同:

Person(person1=SinglePerson(name=hydra, gender=male), person2=SinglePerson(name=susan, gender=female))

SnakeYml其实实现了非常多的功能,这里就不一一列举了,有兴趣的小伙伴可以自己查看一下文档。如果你看了上一篇的文章后跟着翻阅了一下源码,那么你会发现,其实在SpringBoot的底层,也是借助了SnakeYml来进行的yml的解析操作。

5、jackson-dataformat-yaml

相比大家平常用jackson比较多的场景是用它来处理json,其实它也可以用来处理yml,使用前需要引入依赖:

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-yaml</artifactId>
    <version>2.12.3</version>
</dependency>

使用jackson读取yml也非常简单,这里用到了常用的ObjectMapper,在创建ObjectMapper对象时指定使用YAML工厂,之后就可以简单的将yml映射到实体:

public void read() throws IOException {
    ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
    InputStream input =
        new FileInputStream("F:\\Work\\yml\\src\\main\\resources\\snake1.yml");
    Person person = objectMapper.readValue(input, Person.class);
    System.out.println(person.toString());
}

运行结果:

Person(person1=SinglePerson(name=hydra, gender=male), person2=SinglePerson(name=susan, gender=female))

如果想要生成yml文件的话,可以调用ObjectMapperwriteValue方法实现:

public void write() throws IOException {
    Map<String,Object> map=new HashMap<>();
    SinglePerson person1 = new SinglePerson("Trunks", "male");
    SinglePerson person2 = new SinglePerson("Goten", "male");
    Person person=new Person(person1,person2);
    map.put("person",person);

    ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
    objectMapper
            .writeValue(new File("F:\\Work\\yml\\src\\main\\resources\\jackson-gen.yml"),map);
}

查看生成的yml文件,可以看到jackson对字符串类型严格的添加了引号,还在文档的开头添加了yml的链接符

四、操作properties文件

1、Properties类

Properties 类位于 java.util.Properties 中,是Java 语言的处理配置文件所使用的类,其中的 xxx.Properties 类主要用于集中的持久存储Java的配置文件内容,可以读取后缀是 .properties.cfg的配置文件。

Properties 继承了 Hashtable 类,以Map 的形式进行放置值,put(key,value)get(key),文本注释信息可以用”#”来注释。

Properties 类表示了一个持久的属性集。Properties 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。

Properties 文件内容的格式是:键=值 形式,Key值不能够重复。 例如:

jdbc.driver=com.mysql.jdbc.Driver

Properties类继承关系图

它提供了几个核心的方法:

  • getProperty(String key): 用指定的键在此属性列表中搜索属性。也就是通过参数 key ,得到 key 所对应的 value。

  • load(InputStream inStream): 从输入流中读取属性列表(键和元素对)。通过对指定的文件(比如说上面的 test.properties 文件)进行装载来获取该文件中的所有键 - 值对。以供 getProperty(String key) 来搜索。

  • setProperty(String key, String value) : 调用 Hashtable 的方法 put 。他通过调用基类的put方法来设置 键 - 值对。

  • store(OutputStream out, String comments): 以适合使用 load 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。与 load 方法相反,该方法将键 - 值对写入到指定的文件中去。

  • clear(): 清除所有装载的 键 - 值对。该方法在基类中提供。

2、写入Properties

Properties 类调用 setProperty 方法将键值对保存到内存中,此时可以通过 getProperty 方法读取, propertyNames 方法进行遍历,但是并没有将键值对持久化到属性文件中,故需要调用 store 方法持久化键值对到属性文件中。

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Properties;

public class PropertiesStoreTest {
    public static void main(String[] args) {
        Properties properties = new Properties();
        OutputStream output = null;
        try {
            output = new FileOutputStream("src/main/resources/jdbc.properties");
            properties.setProperty("jdbc.driver", "com.mysql.jdbc.Driver");
            properties.setProperty("jdbc.url","jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8" );
            properties.setProperty("jdbc.username", "root");
            properties.setProperty("jdbc.password", "123456");

            // 保存键值对到文件中
            properties.store(output, "rewind modify");

        } catch (IOException io) {
            io.printStackTrace();
        } finally {
            if (output != null) {
                try {
                    output.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

输出结果,在resources目录下多一个文件 jdbc.properties,内容如下:

#rewind modify
#Tue Dec 29 13:43:48 CST 2020
jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8
jdbc.username=root
jdbc.driver=com.mysql.jdbc.Driver
jdbc.password=123456

3、读取Properties

(1)从当前的类加载器读取文件1

/**
* 从当前的类加载器的getResourcesAsStream来获取
* InputStream inputStream = this.getClass().getResourceAsStream(name)
*/

// 读取jdbc.properties配置文件
InputStream inputStream = this.getClass().getResourceAsStream("config/application.properties");
// 或
//InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("config/application.properties");
// 或
//InputStream inputStream = ClassLoader.getSystemResourceAsStream("config/application.properties");

Properties properties = new Properties();
properties.load(inputStream);
properties.list(System.out);

// 获取配置信息
String jdbcUrl = properties.getProperty("jdbc.url");

(2)资源读取

Resource resource = new ClassPathResource("config/application.properties");

Properties properties = PropertiesLoaderUtils.loadProperties(resource);
properties.list(System.out);

String jdbcUrl = properties.getProperty("jdbc.url");

(3)

InputStream inputStream = new BufferedInputStream(new FileInputStream("src/main/resources/config/application.properties"));

Properties properties = new Properties();
properties.load(inputStream);
properties.list(System.out);

String jdbcUrl = properties.getProperty("jdbc.url");

(4)文件输入流

InputStream inputStream = new FileInputStream("src/main/resources/config/application.properties");

Properties properties = new Properties();
properties.load(inputStream);
properties.list(System.out);

String jdbcUrl = properties.getProperty("jdbc.url");

(5)

InputStream inputStream = ClassLoader.getSystemResourceAsStream("config/application.properties");

ResourceBundle resourceBundle = new PropertyResourceBundle(inputStream);
Enumeration<String> keys = resourceBundle.getKeys();
while (keys.hasMoreElements()) {
    String s = keys.nextElement();
    System.out.println(s + " = " + resourceBundle.getString(s));
}

(6)

默认从根目录下读取,也可以读取resources目录下的文件

//ResourceBundle rb = ResourceBundle.getBundle("jdbc"); //读取resources目录下的jdbc.properties
ResourceBundle rb2 = ResourceBundle.getBundle("config/application");//读取resources/config目录下的application.properties
for(String key : rb2.keySet()){
    String value = rb2.getString(key);
    System.out.println(key + ":" + value);
}

  目录