分类
spring spring-boot

Spring 项目配置的几种方法

Properties with Spring and Spring Boot

1. 概述

本文中将讨论Spring项目中如何进行配置以及如何使用配置。文章中将分别展示两种方式:JAVA代码配置、XML配置。

Spring Boot项目完全继承了Spring项目的配置方式,不仅如此,其提供的一种新的配置方式使项目的配置更的高效。

2. 使用Java注解注释配置文件

自Spring 3.1开始,Spring提供了一个新的@PropertySource 用于向项目环境中添加配置源。

该注解在使用时需要@Configuration注解配合工作:

@Configuration
@PropertySource("classpath:sms.properties")
public class SmsProperties {

在上述代码的支撑下,我们便可以在应用中注入Enviroment,并通过调用getProperty()方法来轻松的获取到classpath:sms.properties文件中的配置项了:

    @Autowired
    Environment environment;

    public String getApiUrl() {
        return this.environment.getProperty("apiUrl");
    }

在注册配置源时,当我们需要设置某些数据源仅在某些条件下生效时,则可以使用${}占位符

@Configuration
@PropertySource("classpath:datasource-${spring.application.db-type:mysql}.properties")
public class DatasourceProperties {

classpath:datasource-${spring.application.db-type:mysql}.properties表示,如果spring.application.db-type配置项存在,则使用该配置项的值替代${spring.application.db-type:mysql},如果该配置项不存在,则使用mysql这个默认值来替换${spring.application.db-type:mysql}

假设spring.application.db-type存在且值为h2时,比如我们在application.properties中进行如下配置并提前导入该配置源:

spring.application.name=codedemo
spring.application.db-type=h2

则Spring会自动加载classpath:datasource-h2.properties配置文件。如果spring.application.db-type不存在,则会加载classpath:datasource-mysql.properties配置文件。

2.1 定义多配置源

自java8开始,我们可以在某个目标上重复使用同一个注解。所以当我们想定义多个配置源时,则可以如下使用:

@Configuration
@PropertySource("classpath:foo.properties")
@PropertySource("classpath:bar.properties")
public class FooBarProperties {

上述代码同时将classpath:foo.properties与classpath:bar.properties添加到了配置源中,此时我们便可以在Environment中获取上述两个配置文件定义的配置值了。

除此以外,如果你不习惯上述用法,或是你当前的JAVA版本并不支持在同一目标上定义多个相同注解,则可以使用@PropertySources注解,该注解可以接收数组类型:

@Configuration
@PropertySources({
        @PropertySource("classpath:foo.properties"),
        @PropertySource("classpath:bar.properties")
})
public class FooBarProperties {

值得注意的是:无论我们使用哪种方式定义多配置源,当配置源中的配置项发生冲突时,后面的配置源将覆盖前台的配置源。

3. 使用XML方式注册

使用XML方式配置时配置源时,需要将配置源添加到 <context:property-placeholder … > 元素中:

<context:property-placeholder location="classpath:foo.properties" />

此时Spring应用在启动时将加载/src/main/resources下的foo.properties配置文件。

如果在项目中配置使用 <property-placeholder>设置了多个配置源,那么请参考以下最佳实践:

  1. 应该为每个配置源均指定order属性,在Spring进行加载时,将按照指定的order属性顺序加载。
  2. 除最后一个配置源外(order最大的那个),其实的配置源都应该设置ignore-unresolvable=”true” 。

3.1 使用XML设置多数据源

在XML中,可以使用","来分隔多个配置源:

<context:property-placeholder location="classpath:foo.properties, classpath:bar.properties"/>

同样的,如果多个配置源定义了相同的配置,则只有最后定义的配置源中的配置生效。

4. 直接在项目使用配置值

除了可以注入Environment后调用getProperty方法来获取配置值以外,还可以使用@Value注解

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

注意:上述代码中使用@Value注入了key的值,此时若key存在于sms.properties中而非application.properties中。则必须保证sms.properties已被添加到配置源。

当然了,我们还可以为其提供一个默认值,以避免在未配置此项时发生异常:

    @Value("${token:defaultToken}")
    private String token;

此时当项目未定义token时,token值将被设置为defaultToken

也可以使用XML进行定义:

<bean id="dataSource">
    <property name="token" value="${token:defaultToken}" />
</bean>

更多详细的信息请参考:https://www.baeldung.com/properties-with-spring#usage

5. 配置Spring Boot

在Spring中配置完配置源后,需要通过EnvironmentgetProperty()方法来配置相关的配置项。Spring Boot则提供了一系列更简单、更直接的方法:

5.1 默认配置文件application.properties

Spring Boot在启动时会加载默认的配置的文件application.properties。这意味着:我们只需要在src/main/resources中提供application.properties文件,而不需要使用PropertySource等将其声明为配置源。

同时,当你想使用其它的文件作用项目默认配置文件时,仅仅需要在运行时加入如下参数即可。比如在项目启动时使用src/main/resources中的another-location.properties替换原application.properties文件,则可以使用以下命令启动:

java -jar app.jar --spring.config.location=classpath:/another-location.properties

自Spring Boot 2.3开始,该配置还加入了对正则表达式的支持,比如我们可以如下设置:

java -jar app.jar --spring.config.location=config/*/

此时Spring Boot将会查找所有满足上述正则表达式的配置文件。上述代码将在运行app.jar时,查找所有的当前app.jar所在文件夹/config目录下的配置文件。这种方式特别的适用于为某个应用配置多个配置文件的情景。

5.2 情景环境配置

注意:本文并未刻意的区域情景环境,在大多数文章中情景等于环境,只是说法不同罢了,本文也是如此。

在Spring Boot中可以轻松的为不同的环境定制指定的配置。

我们可以在src/main/resources文件夹中建立application-环境名称.properties文件,Spring应用在启动时将对按项目的当前情景名称选择加载对应的配置文件。

比如我们需要一个用于开发的"开发环境",将其名称起为dev。则需要在src/main/resources文件夹中建立application-dev.properties文件。此时,当项目的情景设置为dev时,此配置文件将被自动加载:

spring.profiles.active=dev

Spring能够非常聪明的处理dev环境:Spring先读取默认的application.properties中的配置,然后再读取当前情景dev对应的application-dev.properties配置。并使用application-dev.properties中定义的配置覆盖application.properties中的相同配置项。

5.3 单元测试中的配置

如果要为单元测试指定单独的配置,则可以在src/test/resources文件夹中创建相应的配置文件。与项目配置的规则相同,当单元测试的配置项发生冲突时,优先给高的配置文件会覆盖优先级低的配置文件。

5.4 @TestPropertySource 注解

我们还可以使用@TestPropertySource注解为单元测试来指定特定的配置文件:

@SpringBootTest
@TestPropertySource("classpath:test.properties")
public class TestWithProperties {

除了指定配置文件外,还可以直接在单元测试中定义属性:

@TestPropertySource(properties = "foo1=bar1")

@SpringBootTest注解也支持属性的定义:

@SpringBootTest(properties = {"foo2=bar2"})

5.5 @ConfigurationProperties 注解

相对于Spring,Spring Boot提供的@ConfigurationProperties注解能够快速的对配置文件按前缀进行分组,并自动将配置文件中的值映射到JAVA类的属性上。

比如我们有如下配置:

code-dome.domain=www.codedemo.club
code-dome.organization=mengyunzhi
code-dome.year=2020

则可以建立如下配置类

@Component
@ConfigurationProperties(prefix = "code-dome")
public class CodeDemoProperties {
    private String domain;
    private String organization;
    private int createYear;
    // 省略getter/setter
}

Spring Boot的@ConfigurationProperties注解的自动将配置中的属性映射到相应的配置类的字段上。而我们需要做的,仅仅是提供配置类的前缀。

如果你想了解更多关于Spring Boot配置映射的知识,还可以学习进阶文章

5.6 YAML文件格式

Spring同样支持YAML文件格式。在使用YAML做为配置的文件格式时,本文所讨论的所有效果基本上都生效。我们要做做仅仅是将.prpperties重命名为.yml,然后按照SnakeYAML规范来配置属性即可。

比如我们在application.properties中有如下配置:

spring.profiles.active=dev
spring.application.name=codedemo
spring.application.db-type=h2

code-dome.domain=www.codedemo.club
code-dome.organization=mengyunzhi
code-dome.create-year=2020

则使用YAML格式应该定义为:

spring:
  profiles:
    active: dev
  application:
    name: codedemo
    db-type: h2

code-dome:
  domain: www.codedemo.club
  organization: mengyunzhi
  create-year: 2020

同时YAML还支持在一个配置文件中定义多个情景模式:

---
# 使用---定义新的情景模式 mengyunzhi
spring:
  profiles: mengyunzhi

code-dome:
  domain: www.yunzhi.club
  organization: mengyunzhi

---
# 使用---定义新的情景模式 hebut
spring:
  profiles: hebut

code-dome:
  domain: www.hebut.edu.cn
  organization: hebut

值得注意的是:@PropertySource注解并不支持YAML格式,如果你使用了@PropertySource注解则必须提供一个.properties文件。

5.7 命令行模式

当我们使用java来启动打包后的项目文件时,可以使用如下命令来覆盖项目的相关配置项:

java -jar app.jar --property="value"

或者使用如下命令来变更系统属性,从而覆盖项目的相关配置项:

java -Dproperty.name="value" -jar app.jar

比如我们需要在运行项目时覆盖数据库的用户名,则可以使用:

java -jar app.jar --spring.datasource.username="newusername"

或者:

java -Dspring.datasource.username="newusername" -jar app.jar

5.8 环境变量

同样的,如果对某个运行JAVA应用的系统设置了相应的环境变量。Spring项目在被启动时,会自动的加载该环境变量,从而覆盖项目的相应的配置:

export code-dome.domain=www.codedemo.club
java -jar app.jar

此时app.jar应用读取到的code-dome.domain的值为www.codedemo.club

5.9 随机值

有时候你可能需要一些随机值,此时可以使用 RandomValuePropertySource,比如使用${random.int}来获取一个随机整型。

# 随机字符串
my.secret=${random.value}
#随机long
my.bignumber=${random.long}
#随机uuid
my.uuid=${random.uuid}
# 随机int
my.number=${random.int}
# 随机小于10
my.number.less.than.ten=${random.int(10)}
# 随机范围
my.number.in.range=${random.int[1024,65536]}

5.10 其它配置方法

除本文所描述的方法外,Spring Boot还支持多种配置方法。预了解更多的详情,请参考Spring Boot官方文档

6. 在Spring 3.0中手动配置

除了使用注解进行项目配置外,我们还可以使用代码进行相关配置。

需要注意的是:使用代码配置不是常规做法,除非你真的认为有必要,否则应该放弃这种又臭又长的配置方式。

比如添加配置文件java-manually.properties

@Bean
public static PropertyPlaceholderConfigurer properties() {
    PropertyPlaceholderConfigurer ppc
      = new PropertyPlaceholderConfigurer();
    Resource[] resources = new ClassPathResource[]
      { new ClassPathResource( "java-manually.properties" ) };
    ppc.setLocations( resources );
    ppc.setIgnoreUnresolvablePlaceholders( true );
    return ppc;
}

使用XML:

<bean 
  class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <list>
            <value>classpath:java-manually.properties</value>
        </list>
    </property>
    <property name="ignoreUnresolvablePlaceholders" value="true"/>
</bean>

7. 在Spring 3.1中手动配置

Spring 3.1启用了PropertySourcesPlaceholderConfigurer替代Spring 3.0中的PropertyPlaceholderConfigurer。在使用方法上大同小异:

    @Bean
    public static PropertySourcesPlaceholderConfigurer properties() {
        PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer
                = new PropertySourcesPlaceholderConfigurer();
        ClassPathResource[] resources = new ClassPathResource[] {
                new ClassPathResource("java-manually.properties")
        };
        propertySourcesPlaceholderConfigurer.setLocations(resources);
        propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(true);
        return propertySourcesPlaceholderConfigurer;
    }

需要注意的是,通过上述方法加载的配置项仅能够通过@Value来获取,若尝试使用Environment获取则将得到一个null值

8. 属性在上下文中的传递规则

属性在上下文中的传送规则是个老生常谈的问题。当我们的应用具有父上下文或子上下文时,父子上下文中可能会具有一些相同的核心功能。这不可避免的会带来一些冲突。那么我们应该如何避免这些冲突并使其朝着正确的方向发展呢?

下面我们给出一些简单的规则 :

8.1 如果配置文件是使用<proprty-placeholder>在XML中定义的

如果文件是在上下文中定义的:

  • @Value 在子上下文中
  • @Value 在父上下文中

如果文件是在上下文中定义的:

  • @Value 在子上下文中
  • @Value 在父上下文中

综上:<property-placeholder>在XML中定义的,仅在本上下文中有效。

最后,正如我们在前面提过的那样,使用<property-placeholder>定义的属性,并不会注入到Environment中,所以使用该方式定义的属性,在父子上下文中均无法通过environment.getProperty()获取。即:

  • environment.getProperty在子上下文中
  • environment.getProperty在父上下文中

8.2 如果属性是通过@PropertySource注解定义的

如果文件是在父上下文中定义的,则:

  • @Value 在子上下文中
  • @Value 在父上下文中
  • environment.getProperty 在父上下文中
  • environment.getProperty 在子上下文中

如果文件是在子上下文中定义的,则:

  • @Value 在子上下文中
  • @Value 在父上下文中
  • environment.getProperty在子上下文中
  • environment.getProperty在父上下文中

9 结论

项目离不开配置,能否快速、高效、准确的对项目进行配置很大程度上影响了项目开发、测试、运维的效率。本文从多方面对Spring、Spring Boot的项目配置进行了介绍,同时给出了多种获取项目配置的方法。在文章的最后对项目配置的上下文传递进行简单的总结。