Springboot可以让你将配置外部化,这样你就可以在不同得环境中使用相同的应用程序代码。你可以使用各种外部配置源,包括Java properties文件,YAML文件,环境变量和额命令行参数。

属性值可以通过使用@Value注解直接注入你的Bean,也可以通过Spring的Environment访问,或者通过@ConfigurationProperties绑定到对象。

Springboot使用一个非常特别的PropertySource顺序,旨在允许合理地重写值。后面property source可以覆盖前面属性中定义的值。按以下顺序考虑。

  1. 默认属性(通过SpringApplication.setDefaultProperties指定)。
  2. @Configuration类上的@PropertySource注解。请注意,这样的属性源直到application context被刷新时才会被添加到环境中。这对于配置某些属性来说已经太晚了。比如logging.*和spring.main.*,它们在刷新开始前就已经被读取了。
  3. 配置数据(如application.properties文件)。
  4. RandomValuePropertySource,它只有random.*属性。
  5. 操作系统环境变量。
  6. Java System properties(System.getProperties())
  7. java:comp/env中的JNDI属性。
  8. ServletContext  init parameters。
  9. ServletConfig  init parameters
  10. 来自SPRIING_APPLICATION_JSON的属性。
  11. 命令行参数。
  12. 你在测试中的properties属性。在@SpringBootTest和测试注解中可用。
  13. @DynamicPropertySource注解在你的测试中。
  14. 当devtools处于活动状态时,$HOME/.config/spring-boot目录下的Devtools全局设置属性。

配置数据文件按以下顺序考虑。

  1. 在你的jar中打包的application.yaml或者application.properties.
  2. 在你的jar中打包的特定的Profile (application-{profile}.properties和yaml)

 ✦ 建议你在整个应用程序中坚持使用一种格式。如果你同时游.properties和yaml格式的配置文件。那么.properties优先。

 ✦ 如果你使用环境变量而不是系统属性,大多数操作系统不允许使用据点分割的键名,但你可以使用下划线代替(例如SPRINIG_CONFIG_NAME代替spring.config.name)。

✦ 如果你的应用程序在servlet容器或应用服务器中运行,那么JNDI属性(在java:comp/env中)或servlet上下文初始化参数可以代替环境变量或系统属性。

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

// 为了提供一个具体的例子,假设你开发了一个 @Component,使用了一个 name 属性,如下面的例子所示。
@Component
public class MyBean {

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

1.1 访问命令行属性

默认情况下,SpringApplication会将任何命令行选项参数转换为property并将其添加到Spring Enviroment中。命令行属性总是有限于基于文件的属性源。

如果你不希望命令行属性被添加到Environment中,你可以通过SpringApplication.setAddCommandLineProperties(false)禁用它们。

1.2 JSON Application Properties

环境变量和系统属性往往有限制,这意味着有些属性名称不能使用。为了帮助我们解决这个问题,Springboot允许你将一个属性块编码为一个单一的JSON结构。

当你的 应用程序启动时,任何spring.application.json或SPRING_APPLICATION_JSON属性将解析并添加到Enviroment中。

例如,SPRING_APPLICATION_JSON属性可以在unix shell的命令行中作为环境变量提供。

在前面的例子中,你在Spring的Enviroment中最终得到了my.name=test。

同样的JSON也可以作为一个系统属性提供。

或者是通过命令行进行传递

1.3 外部的Application Properties 

当你的应用程序启动时,Springboot会自动从以下位置找到并加载application.properties和application.yaml文件。

  1. classpath

    1. classpath 根路径

    2. classpath 下的/config 包

  2. 当前目录

    1. 当前目录下

    2. 当前目录下的config/子目录

    3. config/子目录的直接子目录

列表按优先级别排序,加载的 文件被作为PropertySources添加到Spring的Enviroment中。

如果你不喜欢application 作为配置文件名称,你可以 按以下方式运行你的应用程序。

 你也可以通过使用spring.config.location环境属性来引用一个明确的位置。该属性接受一个逗号分隔的列表,其中包含一个或多个要检查的位置。

 1.3.1 可选的位置

默认情况下,当指定的配置数据位置不存在时,springboot将抛出一个ConfigDataLocationNotFoundException,你的应用程序将无法启动。

如果你想指定一个位置,但你不介意它并不总是存在,你可以使用optional:前缀。你可以在spring.config.location和spring.config.extra-location属性中使用这个前缀,也可以在spring.config.import声明中使用。

例如spring.config.import值为optional:file:./myconfig.properties允许你的应用程序启动,即使myconfig.properties文件丢失。

如果你想要忽略所有的ConfigDataLocationNotFoundExceptions并始终继续启动你的应用程序,你可以使用spring.config.on-not-found属性。使用SpringApplication.setDefaultProperties(...)或使用系统/环境变量将其值设置为ignore。

  1.3.2 通配符地址

如果一个配置文件的位置在最后一个路径段中包含*字符,它就被认为是一个通配符位置。通配符在加载配置时被扩展,因此,直接的子目录也被检查。通配符位置在kubernetes这种有多个配置属性的来源的环境中特别有用。

例如,如果你有一些Redis配置和一些MySQL配置,你可能想把这两部分配置分开,同时要求这两部分都存在于一个application.properties文件中。

这可能会导致两个独立的application.properties文件挂载在不同的位置,如/config/redis/application.properties和/config/mysql/application.properties。在这种情况下有一个通配符位置config/*/,将导致这两个文件被处理。

默认情况下,Springboot将config/*/列入默认搜索位置。这意味着你的jar之外的config目录的所有子目录都会被搜索到。你可以在spring.config.location和spring.config.extra-location属性中使用通配符位置。

  1.3.3 特定文件

除了application属性文件,Springboot还将尝试使用application-{profile}的命名惯例加载profile特定的文件。例如,如果你的应用程序激活了名为prod的配置文件(spring.profiles.active=prod)并使用YAML文件,那么application.yaml和application-prod.yaml都被考虑。

特定文件(profiles)的属性于标准的application.properties的位置相同,特定文件总是优先于非特定文件。如果指定了几个配置文件,则采用最后胜出的策略。例如,如果配置文件prod,live时由spring.profiles.active属性指定的,application-prod.properties中的值可以被application-live.properties中的值所覆盖。

最后胜出的策略适用于location group级别,spring.config.location的classpath:/cfg/,classpath:/ext/将不会有与classpath:/cfg/;classpath:/ext/相同的覆盖规则。

例如拿我们的上面的prod,live例子来说,我们可能有以下文件。

当我们有一个spring.config.location为classpath:/cfg/,classpath:/ext/时,我们会在所有/ext文件之前处理所有/cfg文件。

属性文件只被加载一次。如果你已经直接导入了一个配置文件的特定属性文件,那么它将不会被导入第二次。

1.3.4 导入额外的数据

application.properties中可以使用spring.config.import属性从其他地方导入更多的配置数据。导入在被发现时被处理,并被视为紧接着声明导入的文件下面插入的额外文件。

例如,你可能在你的classpath application.propertis文件中有一下内容。

 这将触发导入当前目录下的dev.properties文件(如果存在这样的文件)。导入的dev.properties中的值将优先于触发导入的文件。在上面的例子中,dev.properties可以将spring.application.name重新定义一个不同的值。

一个导入只会被导入一次,无论它被声明多少次,一个导入再properties/yaml文件内的单个文件被定义的顺序并不重要。

1.4 类型安全得配置属性

使用@Value("${property}") 注解来注入配置属性有时会很麻烦,特别是当你要处理多个属性或你得数据是分层得。Springboot 提供了一种处理属性得替代方法,让强类型得Bean管理和验证你的应用程序的配置。

1.4.1 JavaBean属性绑定

如下面的例子可以绑定一个标准的JavaBean

@ConfigurationProperties("my.service")
public class MyProperties {

    private boolean enabled;

    private InetAddress remoteAddress;

    private final Security security = new Security();

    // getters / setters...

    public static class Security {

        private String username;

        private String password;

        private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
        // getters / setters...
    }
}

前面的POJO定义了以下属性。

  • my.service.enabled,默认值为false
  • my.service.remote-address,其类型可由String强制提供
  • my.service.sercurity.username,有一个嵌套的sercurity对象,其名称由该属性的名称决定。特别是,那里完全没有使用类型,可以是SecurityProperties。
  • my.service.sercurity.password
  • my.service.security.role,有一个String的集合,默认为user。

映射到Springboot中可用的@ConfigurationProperties类的属性,通过properties文件,yaml文件,环境变量和其他机制进行配置,这些属性是公共API。

1.4.2 构造函数绑定

@ConfigurationProperties("my.service")
public class MyProperties {

    // fields...

    public MyProperties(boolean enabled, InetAddress remoteAddress, Security security) {
        this.enabled = enabled;
        this.remoteAddress = remoteAddress;
        this.security = security;
    }

    // getters...

    public static class Security {

        // fields...

        public Security(String username, String password, @DefaultValue("USER") List<String> roles) {
            this.username = username;
            this.password = password;
            this.roles = roles;
        }

        // getters...

    }

}

在这种设置中,唯一的带参数构造函数的存在意味着应该使用该构造函数进行绑定。这意味着绑定器会找到一个带有你希望绑定的参数的构造函数。如果你的类有多个构造函数,可以使用@ConstructorBinding注解来指定使用哪个构造函数进行 构造函数绑定。如果要为一个只有一个带参数 构造函数的类选择不绑定构造函数。该构造函数必须要用@Autowired来注解。构造函数绑定可以与Record一起使用。除非你的记录有多个构造函数,否则没有必要使用@ConstructorBinding 

构造函数绑定类的嵌套成员也将通过其构造函数被绑定。

默认值可以在构造参数和Record组件上使用@DefaultValue来指定。转换服务将被应用于将注解String值强制换行为缺失属性的目标类型。

参考前面的例子,如果没有属性绑定到Security,Myproperties实例将包含一个security类型的null值。为了使它包含一个非null的Security实例,即使没有属性与之绑定,使用一个空的@DefaultValue注解。

1.4.3 启用@ConfigurationProperties类

Springboot提供了绑定@ConfigurationProperties类型并将其注册为Bean的基础设施。你可以在逐个类的基础上启用配置属性,或者启用配置属性 扫描,其工作方式与组件扫描类似。

有时,用@ConfigurationProperties注解的类可能不适合扫描,例如,如果你正在开发你自己的自动配置或者你想有条件地启用它们。在这些情况下,使用EnableConfigurationProperties注解指定要处理的类型列表,它可以注解任何@Configuration类上,如下面的例子所示。

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SomeProperties.class)
public class MyConfiguration {

}
@ConfigurationProperties("some.properties")
public class SomeProperties {

}

要使用配置属性扫描,请向你的application添加@configurationPropertiesScan注解。通常,它被添加到用@SpringBootApplication注解的main类中,但它也可以被添加到任何@Configuration类上。默认情况下,扫描会从注解所在的包开始,你如果自定义扫描其他包,可以参考如下。 

@SpringBootApplication
@ConfigurationPropertiesScan({ "com.example.app", "com.example.another" })
public class MyApplication {

}

我们建议@ConfigurationProperties只处理environment,特别是不从上下文注入其他Bean。对于边角案例,可以使用setter注入或框架提供的任何*Aware接口。如果你扔想使用构造器注入其他Bean,配置属性Bean必须用@Component来注解,并使用基于JavaBean的属性绑定。

03-22 10:18