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>设置了多个配置源,那么请参考以下最佳实践:
- 应该为每个配置源均指定order属性,在Spring进行加载时,将按照指定的order属性顺序加载。
- 除最后一个配置源外(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中配置完配置源后,需要通过Environment的getProperty()方法来配置相关的配置项。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"})
相对于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的项目配置进行了介绍,同时给出了多种获取项目配置的方法。在文章的最后对项目配置的上下文传递进行简单的总结。