第五篇:Spring Boot的特性:外部化配置

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

Spring Boot使用非常特别的PropertySource命令,旨在允许合理地覆盖值。属性按以下顺序选择:

  1. Devtools global settings properties on your home directory (~/.spring-boot-devtools.properties when devtools is active).
  2. @TestPropertySource annotations on your tests.
  3. @SpringBootTest#properties annotation attribute on your tests.
  4. Command line arguments.
  5. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).
  6. ServletConfig init parameters.
  7. ServletContext init parameters.
  8. JNDI attributes from java:comp/env.
  9. Java System properties (System.getProperties()).
  10. OS environment variables.
  11. RandomValuePropertySource that has properties only in random.*.
  12. Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants).
  13. Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants).
  14. Application properties outside of your packaged jar (application.properties and YAML variants).
  15. Application properties packaged inside your jar (application.properties and YAML variants).
  16. @PropertySource annotations on your @Configuration classes.
  17. Default properties (specified by setting SpringApplication.setDefaultProperties).

一个具体的例子,假设你开发一个使用name属性的@Component

import org.springframework.stereotype.*
import org.springframework.beans.factory.annotation.*
 
@Component
publicclass MyBean {
 
    @Value("${name}")
    private String name;
 
    // ...
 
}

在应用程序类路径(例如,您的jar中)中,您可以拥有一个application.properties,它为 name 属性提供了默认属性值。在新环境中运行时,可以在您的jar外部提供一个application.properties来覆盖 name 属性; 对于一次性测试,您可以使用特定的命令行开关启动(例如, java -jar app.jar --name="Spring")。

SPRING_APPLICATION_JSON properties 可以在命令行中提供一个环境变量。比如在 UN*X 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

 配置随机值

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

 

random.int *语法是OPEN value (,max) CLOSE ,其中OPENCLOSE是任何字符和值,max是整数。如果提供max,则值为最小值,max为最大值(独占)。

访问命令行属性

默认情况下,SpringApplication将任何命令行选项参数(以‘--’, 开头,例如--server.port=9000)转换为属性,并将其添加到Spring环境中。如上所述,命令行属性始终优先于其他属性来源。

如果不希望将命令行属性添加到环境中,可以使用SpringApplication.setAddCommandLineProperties(false)禁用它们。


应用程序属性文件(Application Property Files)

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

  1. 当前目录的/config子目录
  2. 当前目录
  3. classpath中/config包
  4. classpath  root路径

该列表按优先级从高到低排序。

也可以使用YAML’.yml’)文件替代“.properties”


如果您不喜欢application.properties作为配置文件名,可以通过指定一个spring.config.name Spring environment属性来切换到另一个。 您还可以使用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一开始就被用于确定哪些文件必须被加载,因此必须将它们定义为环境属性(通常是OS envsystem属性或命令行参数)。

如果spring.config.location包含目录(而不是文件),那么它们应该以/结尾(并将在加载之前附加从spring.config.name生成的名称,包括profile-specific的文件名)。spring.config.location中指定的文件按原样使用,不支持特定于配置文件的变体,并且将被任何特定于配置文件的属性覆盖。

默认的搜索路径classpath:,classpath:/config,file:,file:config/ 始终会被搜索,不管spring.config.location的值如何。该搜索路径从优先级排序从低到高(file:config/最高)。如果您指定自己的位置,则它们优先于所有默认位置,并使用相同的从最低到最高优先级排序。这样,您可以在application.properties(或使用spring.config.name选择的任何其他基础名称)中为应用程序设置默认值,并在运行时使用不同的文件覆盖它,并保留默认值。

如果您使用环境(environment)变量而不是系统属性,大多数操作系统不允许使用句点分隔(period-separated)的键名称,但可以使用下划线(例如,SPRING_CONFIG_NAME,而不是spring.config.name

如果您运行在容器中,则可以使用JNDI属性(在 java:comp/env 中)或servlet上下文初始化参数,而不是环境变量或系统属性。


指定配置(Profile-specific)的属性


除了application.properties文件外,还可以使用命名约定application- {profile} .properties定义的特定配置文件。环境具有一组默认配置文件,如果没有设置活动配置文件(即,如果没有显式激活配置文件,则加载了来自application-default.properties的属性)。

指定配置文件(Profile-specific)的属性从与标准application.properties相同的位置加载,特定配置(profile-specific)文件始终覆盖非特定文件,而不管特定配置文件是否在打包的jar内部或外部。

如果指定了几个配置文件,则应用最后一个配置。例如,由spring.profiles.active属性指定的配置文件在通过SpringApplicationAPI配置的配置之后添加,因此优先级高。

如果您在spring.config.location中指定了任何文件,则不会考虑这些特定配置(profile-specific)文件的变体。如果您还想使用特定配置(profile-specific)文件的属性,请使用spring.config.location中的目录。


properties文件中的占位符

application.properties中的值在使用时通过已有的环境( Environment)进行过滤,以便您可以引用之前定义的值(例如,从系统属性)。

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

您也可以使用此技术创建现有SpringBoot属性的简写有关详细信息,请参见 Section 74.4, “Use ‘Short’ Command LineArguments”how-to for details.

使用YAML替代 Properties

YAMLJSON的超集,因此这是分层配置数据一种非常方便的格式,。每当您的类路径中都有SnakeYAML库时,SpringApplication类将自动支持YAML作为 properties 的替代方法。

如果您使用“Starters”SnakeYAML将通过spring-boot-starter自动提供。

 

加载 YAML

Spring Framework提供了两个方便的类,可用于加载YAML文档。 YamlPropertiesFactoryBeanYAML作为Properties加载,YamlMapFactoryBeanYAML作为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]dereferencers的属性键,例如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")
publicclass Config {
 
    private List<String> servers = new ArrayList<String>();
 
    public List<String> getServers() {
        returnthis.servers;
    }
}

 

Extra care is required when configuring lists that way as overriding will not work as you would expect. In the example above, when my.servers is redefined in several places, the individual elements are targeted for override, not the list. To make sure that a PropertySource with higher precedence can override the list, you need to define it as a single property:

my:

   servers: dev.bar.com,foo.bar.com

YAML作为Spring环境中的属性文件

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

多个YAML文件

您可以使用spring.profiles键指定单个文件中的多个特定配置文件YAML文档,以指示文档何时应用。例如:

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

在上面的示例中,如果开发配置文件处于活动状态,则server.address属性将为127.0.0.1如果开发和生产配置文件未启用,则该属性的值将为192.168.1.100

如果应用程序上下文启动时没有显式激活,默认配置文件将被激活。所以在这个YAML中,我们为security.user.password设置一个仅在默认配置文件中可用的值:

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

使用“spring.profiles”元素指定的Spring profiles 以选择使用 字符。如果为单个文档指定了否定和非否定的配置文件,则至少有一个非否定配置文件必须匹配,没有否定配置文件可能匹配。

whereas in this example, the password is always set because itisn’t attached to any profile, and it would have to be explicitly reset in allother profiles as necessary:

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

Spring profiles designated using the "spring.profiles"element may optionally be negated using the ! character. If both negated and non-negated profiles arespecified for a single document, at least one non-negated profile must matchand no negated profiles may match.

 

YAML的缺点

YAML文件无法通过@PropertySource注解加载。因此,在需要以这种方式加载值的情况下,需要使用properties文件。

合并YAML列表

如上所述,任何YAML内容最终都会转换为属性。当通过配置文件覆盖列表属性时,该过程可能比较直观。

例如,假设名称和描述属性默认为空的MyPojo对象。让我们从FooProperties中公开MyPojo的列表:

@ConfigurationProperties("foo")
publicclass FooProperties {
 
    privatefinal List<MyPojo> list = new ArrayList<>();
 
    public List<MyPojo> getList() {
        returnthis.list;
    }
 
}

类比以下配置:

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

如果dev配置没有激活,FooProperties.list将包含一个如上定义的MyPojo条目。如果启用了配置文件,列表仍将包含一个条目(名称为“myanother name”description=null)。此配置不会将第二个MyPojo实例添加到列表中,并且不会将项目合并。

当在多个配置文件中指定集合时,使用具有最高优先级的集合(并且仅使用该配置文件):

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

在上面的示例中,考虑到dev配置文件处于激活状态,FooProperties.list将包含一个MyPojo条目(名称为“my another name”description=null)。


类型安全的配置属性

使用@Value(“${property}”)注释来注入配置属性有时可能很麻烦,特别是如果您正在使用多个层次结构的属性或数据时。 SpringBoot提供了一种处理属性的替代方法,允许强类型Bean管理并验证应用程序的配置。

package com.example;
 
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
import org.springframework.boot.context.properties.ConfigurationProperties;
 
@ConfigurationProperties("foo")
publicclass FooProperties {
 
    privateboolean enabled;
 
    private InetAddress remoteAddress;
 
    privatefinal Security security = new Security();
 
    publicboolean isEnabled() { ... }
 
    publicvoid setEnabled(boolean enabled) { ... }
 
    public InetAddress getRemoteAddress() { ... }
 
    publicvoid setRemoteAddress(InetAddress remoteAddress) { ... }
 
    public Security getSecurity() { ... }
 
    publicstaticclass Security {
 
        private String username;
 
        private String password;
 
        private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
 
        public String getUsername() { ... }
 
        publicvoid setUsername(String username) { ... }
 
        public String getPassword() { ... }
 
        publicvoid setPassword(String password) { ... }
 
        public List<String> getRoles() { ... }
 
        publicvoid setRoles(List<String> roles) { ... }
 
    }
}

上述POJO定义了以下属性:

  • foo.enabled,默认为false
  • foo.remote-address,具有可以从String强转的类型
  • foo.security.username,具有内置的“安全性(security)”,其名称由属性名称决定。 特别是返回类型并没有被使用,可能是SecurityProperties
  • foo.security.password
  • foo.security.roles,一个String集合

Getterssetter方法通常是必须要有的,因为绑定是通过标准的Java Beans属性描述符,就像在Spring MVC中一样。在某些情况下可能会省略setter方法:

  • Map 只要它们被初始化,需要一个getter,但不一定是一个setter,因为它们可以被binder修改。
  • 集合和数组可以通过索引(通常使用YAML)或使用单个逗号分隔值(Properties中)来访问。 在后一种情况下,setter方法是强制性的。 我们建议总是为这样的类型添加一个设置器。 如果您初始化集合,请确保它不是不可变的(如上例所示)
  • 如果已初始化嵌套POJO属性(如上例中的Security字段),则不需要setter方法。如果您希望binder使用其默认构造函数即时创建实例,则需要一个setter。

有些人使用ProjectLombok自动添加gettersetter确保Lombok不会为这种类型生成任何特定的构造函数,因为构造函将被容器自动用于实例化对象。

另请参阅@Value@ConfigurationProperties之间的不同

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

@Configuration
@EnableConfigurationProperties(FooProperties.class)
publicclass MyConfiguration {
}

@ConfigurationProperties bean以这种方式注册时,该bean将具有常规名称:<prefix>- <fqn>,其中是@ConfigurationProperties注解中指定的环境密钥前缀,是bean的全名(fullyqualified name)如果注解不提供任何前缀,则仅使用该bean的全名。上面示例中的bean名称将是foo-com.example.FooProperties

即使上述配置将为FooProperties创建一个常规bean,我们建议@ConfigurationProperties仅处理环境,特别是不从上下文中注入其他bean话虽如此,@EnableConfigurationProperties注释也会自动应用于您的项目,以便使用@ConfigurationProperties注释的任何现有的bean都将从环境配置。您可以通过确保FooProperties已经是一个bean来快速上面的MyConfiguration

@Component
@ConfigurationProperties(prefix="foo")
publicclass FooProperties {
 
    // ... see above
 
}

这种配置方式与SpringApplication外部的YAML配置相当:

# application.yml
 
foo:
    remote-address: 192.168.1.1
    security:
        username: foo
        roles:
          - USER
          - ADMIN
 
# additional configuration as required

要使用@ConfigurationProperties bean,您可以像其他任何bean一样注入它们。

@Service
publicclass MyService {
 
    privatefinal FooProperties properties;
 
    @Autowired
    public MyService(FooProperties properties) {
        this.properties = properties;
    }
 
     //...
 
    @PostConstruct
    publicvoid openConnection() {
        Server server = new Server(this.properties.getRemoteAddress());
        // ...
    }
 
}

使用@ConfigurationProperties还可以生成IDE可以为自己的密钥提供自动完成的元数据文件,有关详细信息,请参见附录B,配置元数据附录

第三方配置

除了使用@ConfigurationProperties来注解类,还可以在public@Bean方法中使用它。当您希望将属性绑定到不受控制的第三方组件时,这可能特别有用。

@ConfigurationProperties(prefix = "bar")
@Bean
public BarComponent barComponent() {
    ...
}

使用 bar 前缀定义的任何属性将以与上述FooProperties示例类似的方式映射到该BarComponentbean

宽松的绑定

Spring Boot使用一些宽松的规则将环境属性绑定到@ConfigurationProperties bean,因此不需要在Environment属性名称和bean属性名称之间进行完全匹配。常用的例子是这样有用的:虚分离(例如上下文路径绑定到contextPath)和大写(例如PORT绑定到端口)环境属性。

例如,给定以下@ConfigurationProperties类:

@ConfigurationProperties(prefix="person")
publicclass OwnerProperties {
 
    private String firstName;
 
    public String getFirstName() {
        returnthis.firstName;
    }
 
    publicvoid setFirstName(String firstName) {
        this.firstName = firstName;
    }
 
}

 

可以使用以下属性名称:

表格 relaxed binding

Property

Note

person.firstName

标准骆峰命名法。

person.first-name

虚线符号,推荐用于.properties.yml文件。

person.first_name

下划线符号,用于.properties.yml文件的替代格式。

PERSON_FIRST_NAME

大写格式推荐使用系统环境变量时。

 

属性转换

Spring绑定到@ConfigurationProperties bean时,Spring将尝试将外部应用程序属性强制为正确的类型。如果需要自定义类型转换,您可以提供ConversionServicebean(使用bean id conversionService)或自定义属性编辑器(通过CustomEditorConfigurer bean)或自定义转换器(使用注释为@ConfigurationPropertiesBindingbean定义)。

由于在应用程序生命周期期间非常早请求此Bean,请确保限制ConversionService正在使用的依赖关系。通常,您需要的任何依赖关系可能无法在创建时完全初始化。如果配置密钥强制不需要,只需依赖使用@ConfigurationPropertiesBinding限定的自定义转换器,就可以重命名自定义ConversionService

@ConfigurationProperties验证

Spring@Validated注释解时,SpringBoot将尝试验证@ConfigurationProperties类。您可以直接在配置类上使用JSR-303javax.validation约束注释。只需确保您的类路径中符合JSR-303实现,然后在您的字段中添加约束注释:

@ConfigurationProperties(prefix="foo")
@Validated
publicclass FooProperties {
 
    @NotNull
    private InetAddress remoteAddress;
 
    // ... getters and setters
 
}

为了验证嵌套属性的值,您必须将关联字段注释为@Valid以触发其验证。例如,基于上述FooProperties示例:

@ConfigurationProperties(prefix="connection")
@Validated
publicclass FooProperties {
 
    @NotNull
    private InetAddress remoteAddress;
 
    @Valid
    privatefinal Security security = new Security();
 
    // ... getters and setters
 
    publicstaticclass Security {
 
        @NotEmpty
        public String username;
 
        // ... getters and setters
 
    }
 
}

您还可以通过创建名为configurationPropertiesValidatorbean定义来添加自定义的SpringValidator @Bean方法应声明为static配置属性验证器在应用程序的生命周期早期创建,并声明@Bean方法,因为static允许创建bean,而无需实例化@Configuration类。这避免了早期实例化可能引起的任何问题。这里有一个属性验证的例子,所以你可以看到如何设置。

spring-boot-actuator模块包括一个暴露所有@ConfigurationProperties bean的端点。只需将您的Web浏览器指向/configprops或使用等效的JMX端点。请参阅生产就绪功能 细节。

 

@ConfigurationProperties 对比 @Value

@Value是核心容器功能,它不提供与类型安全配置属性相同的功能。下表总结了@ConfigurationProperties@Value支持的功能:

功能

@ConfigurationProperties

@Value

Relaxed binding

Yes

No

Meta-data support

Yes

No

SpEL evaluation

No

Yes

如果您为自己的组件定义了一组配置密钥,我们建议您将其分组到使用@ConfigurationProperties注释的POJO中。还请注意,由于@Value不支持宽松的绑定,如果您需要使用环境变量提供值,那么它不是一个很好的选择。

最后,当您可以在@Value中编写一个Spel表达式时,这些表达式不会从应用程序属性文件中处理。

猜你喜欢

转载自blog.csdn.net/mzh_cn/article/details/80579107
今日推荐