24. 外部配置

Spring Boot允许你进行外部化配置,因此可以将同样的应用代码在不同的环境中运行。你可以使用属性文件,YAML文件,环境变量和命令行参数来进行外部化配置。属性值可以使用@Value注解直接注入到你的beans中,通过Spring的Environment抽象或通过@ConfigurationProperties绑定到结构化对象上来访问。

Spring Boot使用非常特别的PropertySource顺序,这个顺序的设计是为了允许值的合理重写。属性被认为是按照以下顺序:

  1. 根目录下的开发工具全局设置属性(当开发工具激活时为~/.spring-boot-devtools.properties)。
  2. 测试中的@TestPropertySource注解。
  3. 测试中的@SpringBootTest#properties注解特性。
  4. 命令行参数。
  5. SPRING_APPLICATION_JSON中的属性(环境变量或系统属性中的内联JSON嵌入)。
  6. ServletConfig初始化参数。
  7. ServletContext初始化参数。
  8. java:comp/env的JNDI特性。
  9. Java系统属性 (System.getProperties())。
  10. 操作系统环境变量。
  11. RandomValuePropertySource只在random.*中有属性。
  12. jar包之外的指定配置文件的应用属性(application-{profile}.properties和YAML变量)。
  13. jar包之内的指定配置文件的应用属性(application-{profile}.properties和YAML变量)。
  14. jar包之外的应用属性 (application.properties和YAML变量)。
  15. jar包之内的应用属性 (application.properties和YAML变量)。
  16. @Configuration类中的@PropertySource注解 。
  17. 默认属性(通过SpringApplication.setDefaultProperties指定).

为了提供一个具体的例子,假设你开发了一个使用名字属性的@Component

import org.springframework.stereotype.*
import org.springframework.beans.factory.annotation.*

@Component
public class MyBean {

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

    // ...

}

在你的应用路径中(例如在你的jar内部),你可以使用application.propertiesname提供一个合理的默认属性值。当在新环境运行时,application.properties可以在jar外部提供来重写name;对于一次性测试,你可以通过指定的命令行切换来启动(例如java -jar app.jar --name="Spring")。

SPRING_APPLICATION_JSON可以在命令行中通过环境变量提供。例如在UNIX shell中:

$ SPRING_APPLICATION_JSON='{"foo":{"bar":"spam"}}' java -jar myapp.jar

在这个例子中,Spring的Environment中会有foo.bar=spam。你也可以在系统变量中提供JSON作为spring.application.json

$ java -Dspring.application.json='{"foo":"bar"}' -jar myapp.jar

或命令行参数:

$ java -jar myapp.jar --spring.application.json='{"foo":"bar"}'

或JNDI变量java:comp/env/spring.application.json

24.1 配置随机值

当注入随机值时,RandomValuePropertySource是很有用的(例如,注入秘密或测试用例)。它可以产生integerslongsuuidsstrings,例如:

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]}

random.int*语法OPEN value (,max) CLOSEOPEN,CLOSE可以是任何字符,value,max是整数。如果提供了max,则value是最小值,max是最大值(不包含)。

24.2 访问命令行属性

默认情况下,SpringApplication会将任何命令行参数(以--开头,例如--server.port=9000)转换成property并将其添加到Spring的Environment中。正如前面提到的那样,命令行属性总是优先于其它的配置源。

如果你想命令行属性添加到Environment中,你可以使用SpringApplication.setAddCommandLineProperties(false)禁用它。

24.3 应用属性文件

SpringApplication会从以下位置的application.properties文件中加载属性并将它们添加到Spring的Environment中:

  1. 当前目录的子目录/config
  2. 当前目录
  3. classpath中的/config
  4. classpath的根目录

这个列表是按优先级排序的(在更高位置的属性会重写定义在更低位置的属性)。

你也可以使用YAML(.yml)文件来代替.properties文件。

如果你不喜欢用application.properties作为配置文件的名字,你可以通过指定spring.config.name环境属性来来改变配置文件的名字。你也可以使用spring.config.location环境属性来引用一个显式的位置(目录位置或文件路径以逗号分隔)。

$ java -jar myproject.jar --spring.config.name=myproject

$ java -jar myproject.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties

spring.config.namespring.config.location可以在早期用来决定加载哪一个文件,因此必须被定义为环境属性(通常是操作系统环境,系统属性或命令行参数)。

如果spring.config.location包含目录(相对于文件而言),它们应该以/结尾(在加载之前,在后面添加上从spring.config.name中产生的名字,包括指定配置文件的名字)。在spring.config.location中指定的文件按原样使用,不支持指定配置文件变量,将会被任何指定配置文件的属性覆盖。

默认搜索路径一直用classpath:,classpath:/config,file:,file:config/,不管spring.config.location中的值。搜索路径按从低到高排序(file:config/最高)。如果你指定了自己的位置,它们优先于所有的默认位置并使用同样的从低到高的顺序。这样你可以在application.properties中为你的应用设置默认值(或你可以选择spring.config.name的其它生成文件基本名),在运行时用其它的文件覆盖它,同时保留默认值。

如果你使用环境变量而不是系统属性,大多数操作系统不允许句号分隔的关键字,但你可以用下划线代替(例如,SPRING_CONFIG_NAME代替spring.config.name)。

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

24.4 特定的profile属性

除了application.properties文件之外,特定的profile属性也可以使用命名规范application-{profile}.properties来定义。Environment有一系列默认配置文件(默认为[default]),如果没有设置激活的配置文件,会使用默认的配置文件(例如,如果没有激活显式的配置文件,则会加载application-default.properties中的属性)。

特定的profile属性会从相同位置加载application.properties,特定的profile文件总是覆盖非特定的配置文件,无论特定profile文件在你打包的jar内部还是外部。

如果指定了几个配置文件,将会应用最后一个。例如,spring.profiles.active属性指定的配置文件在那些配置的文件之后通过SpringApplication API添加,因此优先级更高。

如果你在spring.config.location中指定了任何文件,那些文件的特定profile版本将不会被考虑。如果你也想使用特定的profile属性,在spring.config.location中使用目录。

24.5 属性中的占位符

当使用application.properties中的值时,会通过现有的Environment进行过滤,因此你可以参考前面定义的值(例如从系统属性中)。

app.name=MyApp
app.description=${app.name} is a Spring Boot application

你也可以使用这个技术来创建现有的Spring Boot属性的short版本。怎样使用的更多细节请看70.4小节,“Use ‘short’ command line arguments”。

24.6 使用YAML代替Properties

YAML是JSON的超集,它可以用一种非常方便的形式来指定分层配置数据。当你的类路径有SnakeYAML库时,SpringApplication类自动支持YAML作为properties的一个替代品。

如果你使用‘Starters’,SnakeYAML将由spring-boot-starter自动提供。

24.6.1 加载YAML

Spring框架提供了两个类用来方便的加载YAML文档。YamlPropertiesFactoryBean将加载YAML作为PropertiesYamlMapFactoryBean将加载YAML作为Map

例如,下面的YAML文档:

environments:
    dev:
        url: http://dev.bar.com
        name: Developer Setup
    prod:
        url: http://foo.bar.com
        name: My Cool App

将被转换成这些属性:

environments.dev.url=http://dev.bar.com
environments.dev.name=Developer Setup
environments.prod.url=http://foo.bar.com
environments.prod.name=My Cool App

YAML列表通过[index]解引用表示为属性的key,例如这个YAML:

my:
   servers:
       - dev.bar.com
       - foo.bar.com

将被转换成这些属性:

my.servers[0]=dev.bar.com
my.servers[1]=foo.bar.com

为了像使用Spring的DataBinder一样(@ConfigurationProperties的功能)绑定这些属性,你需要在类型为java.util.List(或Set)的目标bean中有属性,你需要提供一个setter或用一个可变的值来对它初始化,例如,绑定上面的属性值:

@ConfigurationProperties(prefix="my")
public class Config {

    private List<String> servers = new ArrayList<String>();

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

24.6.2 在Spring Environment中公开YAML为属性

YamlPropertySourceLoader类可以在Spring的Environment中将YAML作为PropertySource。这允许你使用熟悉的@Value注解和占位符语法来访问YAML属性。

24.6.3 多profile的YAML文档

你可以在单个文件中指定多个特定profile的YAML文档,当应用文档时,通过spring.profiles关键字来表明使用哪个文档。例如:

server:
    address: 192.168.1.100
---
spring:
    profiles: development
server:
    address: 127.0.0.1
---
spring:
    profiles: production
server:
    address: 192.168.1.120

在上面的例子中,如果development profile被激活,server.address的值为127.0.0.1。如果developmentproduction profile不可用,server.address的值为192.168.1.100

当应用上下文启动时,如果没有显式的激活profile,则激活默认的profile。因此在这个YAML文件中我们仅在”default” profile中设置了security.user.password

server:
  port: 8000
---
spring:
  profiles: default
security:
  user:
    password: weak

在这个例子中,密码总是设置的,因为它没有添加到如何profile中,必要时我们必须在其它的profile中显式的对它重新设置:

server:
  port: 8000
security:
  user:
    password: weak

Spring profiles被设计为使用”spring.profiles”元素可以选择性的用!字符进行否定。如果否定的和非否定的profile指向一个单独的文档,必须至少匹配一个非否定的profile,可能没有否定的profile进行匹配。

24.6.4 YAML缺点

YAML文件不能通过@PropertySource注解进行加载。因此在这种情况下如果你需要加载值,你需要使用属性文件。

24.6.5 合并YAML列表

正如我们上面看到的,任何YAML内容最终都要转换成属性。当通过profile重写“list“属性时,这个过程可能有违直觉。

例如,假设MyPojo对象的namedescription属性默认情况下为空。从FooProperties使用一个MyPojo列表:

@ConfigurationProperties("foo")
public class FooProperties {

    private final List<MyPojo> list = new ArrayList<>();

    public List<MyPojo> getList() {
        return this.list;
    }

}

考虑下面的配置:

foo:
  list:
    - name: my name
      description: my description
---
spring:
  profiles: dev
foo:
  list:
    - name: my another name

如果dev profile没有激活,FooProperties.list将包含一个上面定义的MyPojo输入。然而如果dev profile可用,lists仍只包含一个输入(name为“my another name”,description为空)。这个配置将不能添加第二个MyPojolist中,并且它将不能合并这些项。

当在多个profiles中指定一个集合时,将会使用最高优先级的那个集合(唯一的哪个):

foo:
  list:
    - name: my name
      description: my description
    - name: another name
      description: another description
---
spring:
  profiles: dev
foo:
  list:
     - name: my another name

在上面的例子中,假设dev profile被激活,FooProperties.list将包含一个MyPojo输入(name为“my another name”,description为空)。

24.7 类型安全的配置属性

Boot提供了一种处理属性的可替代方法,允许强类型的beans管理和验证你的应用的配置。例如:

@ConfigurationProperties(prefix="connection")
public class ConnectionProperties {

    private String username;

    private InetAddress remoteAddress;

    // ... getters and setters

}

建议添加getters和setters,绑定是通过标准的Java Beans属性描述符,像在Spring MVC中一样。对于不可变类型或那些从String中可直接强制转换的类型,它们是强制性的。只要它们被初始化,maps,集合或数组需要getter方法,但不需要setter方法因为通过绑定起它们可以直接变化。如果有setter,可以创建Maps,集合和数组。Maps和集合可以通过getter扩展,数组扩展需要setter。如果它们有默认的构造函数,或构造函数接收可以从String类型强制转换的值,嵌入的POJO属性也可以创建(因此setter不是强制性的)。一些人使用Lombok项目来自动添加getter和setter。

 

请看@Value@ConfigurationProperties之间的不同。

你也需要在@EnableConfigurationProperties注解中列出要注册的属性类:

@Configuration
@EnableConfigurationProperties(ConnectionProperties.class)
public class MyConfiguration {
}

@ConfigurationProperties以那种方式注册时,这个bean将有一个常规的名字:<prefix>-<fqn><prefix>是在@ConfigurationProperties注解中指定的环境关键字的前缀,<fqn>是bean的完整合格的名字。如果注解没有提供任何前缀,则只用bean的完整合格的名字。

在上面的例子中bean名字是connection-com.example.ConnectionProperties,假设ConnectionPropertiescom.example包中。

即使上面的配置会为ConnectionProperties创建一个正规的bean,我们建议@ConfigurationProperties只处理环境,特别是不从上下文中注入其它的beans。已经说过,为了任何带有@ConfigurationProperties注解的bean可以根据Environment属性进行配置,@EnableConfigurationProperties注解也自动应用到你的工程中。确保ConnectionProperties已经是一个bean,你可以简写上面的MyConfiguration

@Component
@ConfigurationProperties(prefix="connection")
public class ConnectionProperties {

    // ... getters and setters

}

这种风格的配置在SpringApplication的外部化YAML配置中工作的尤其好:

# application.yml

connection:
    username: admin
    remoteAddress: 192.168.1.1

# additional configuration as required

为了同@ConfigurationProperties beans一起工作,你可以像任何其它bean一样以相同的方式注入它们:

@Service
public class MyService {

    private final ConnectionProperties connection;

    @Autowired
    public MyService(ConnectionProperties connection) {
        this.connection = connection;
    }

     //...

    @PostConstruct
    public void openConnection() {
        Server server = new Server();
        this.connection.configure(server);
    }

}

使用@ConfigurationProperties也允许你生成IDEs可以使用的元数据文件。更多细节请看附录B,配置元数据附录。

24.7.1 第三方配置

也可以使用@ConfigurationProperties来注解一个类,你也可以在公有的@Bean方法上使用它。当你想绑定属性到你控制之外的第三方组件上时尤其有用。

@ConfigurationProperties(prefix = "foo")
@Bean
public FooComponent fooComponent() {
    ...
}

任何定义的带有foo前缀的属性都将以类似于上面的ConnectionProperties例子中的方式映射到FooComponent bean中。

24.7.2 松散绑定

Spring Boot使用一些松散的规则将Environment属性绑定到@ConfigurationProperties beans上,因此不需要在Environment属性名和bean属性名之间进行确切的匹配。常见的有用例子包括破折号分隔(例如,context-path绑定到contextPath),大小写(例如PORT绑定到port,)环境属性。

例如,给定下面的@ConfigurationProperties类:

@ConfigurationProperties(prefix="person")
public class OwnerProperties {

    private String firstName;

    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

}

下面的属性名都可以使用:

表24.1. 松散绑定

Property

Note

person.firstName

标准的驼峰写法

person.first-name

破折号注解,建议在.properties.yml文件中使用

person.first_name

下划线注解,.properties.yml文件中的可替代写法

PERSON_FIRST_NAME

大写形式。当使用系统变量时推荐

24.7.3 属性转换

当Spring绑定属性到@ConfigurationProperties beans时,它将试图将外部的应用属性强制转换成正确的类型。如果你需要定制类型转换你可以提供一个ConversionService bean(bean id为conversionService),或定制属性编辑器(通过CustomEditorConfigurer bean),或定制Converters(带有@ConfigurationPropertiesBinding注解的bean定义)。

bean要求在应用生命周期中的早期,要确保限制ConversionService使用的依赖。通常,任何你要求的依赖可能在创建时不是完整初始化的。如果你定制的ConversionService不要求配置关键字强制转换,你可能想重新命名你定制的ConversionService,并且只依赖满足@ConfigurationPropertiesBindings的定制转换器。

24.7.4 @ConfigurationProperties验证

Spring Boot会试图验证外部化配置,默认使用JSR-303(如果它在classpath中)。你可以简单的添加JSR-303 javax.validation约束注解到你的@ConfigurationProperties类中:

@ConfigurationProperties(prefix="connection")
public class ConnectionProperties {

    @NotNull
    private InetAddress remoteAddress;

    // ... getters and setters

}

为了验证嵌入的属性值,你必须注解相关的字段作为@Valid来触发它的校验。例如,在上面的ConnectionProperties例子上构建:

@ConfigurationProperties(prefix="connection")
public class ConnectionProperties {

    @NotNull
    @Valid
    private RemoteAddress remoteAddress;

    // ... getters and setters

    public static class RemoteAddress {

        @NotEmpty
        public String hostname;

        // ... getters and setters

    }

}

通过创建一个称为configurationPropertiesValidator的bean定义,你也可以添加定制的Spring Validator@Bean方法应该声明静态的。配置属性验证器在应用生命周期的早期创建,声明@Bean方法为静态方法,允许不必实例化@Configuration类就创建bean。这避免了任何早期实例化可能引起的问题。这儿有一个属性验证的例子因此你可以看一下怎样设置它

spring-boot-actuator模块包含一个端点,这个端点将公开所有的@ConfigurationProperties beans。简单的将你的web浏览器指向/configprops或用等价的JMX端点。更多细节请看产品级功能

24.7.5 @ConfigurationProperties和@Value

@Value是一种核心的容器功能,它不能作为类型安全配置属性提供同样的功能。下面的表中总结了@ConfigurationProperties@Value支持的功能:

功能

@ConfigurationProperties

@Value

松散绑定

Yes

No

元数据支持

Yes

No

SpEL评估

No

Yes

如果你为自己的组件定义了一些配置关键字,我们建议你将它们分组到带有@ConfigurationProperties注解的POJO中。也请注意@Value不支持松散绑定,如果你需要用环境变量提供值,它不是一个好的选择。

最后,虽然你可以在@Value中写表达式,但这种表达式不能从应用属性文件中处理。