Spring Boot允许外化(externalize)你的配置,这样你能够在不同的环境下使用相同的代码。
你可以使用properties文件,YAML文件,环境变量和命令行参数来外化配置。使用@Value注解,可以直接将属性值注入到你的beans中,并通过Spring的Environment抽象或绑定到结构化对象来访问。
1.springboot项目内读取配置文件的属性值
springboot 默认读取srcmain esourcesapplication.properties;
例如:
我们在配置文件中准备了2个配置字段,如下:
com.zjt.welcomeWords01="你好,我是com.zjt.welcomeWords01"
com.zjt.welcomeWords02="你好,我是com.zjt.welcomeWords02"
我想要在controller中读取到这连个配置文件的值,并通过rest返回到前台,我们可以直接在controller中定义变量,
并在变量上声明一个这样的注解: @Value(value="${com.zjt.welcomeWords01}"),便可获取到配置文件中的值。
如:这样编写controller:
@RestController public class Controller01 { @Value(value="${com.zjt.welcomeWords01}") private String word01; @Value(value="${com.zjt.welcomeWords02}") private String word02; @ResponseBody @RequestMapping("test01") public String test01(){ System.out.println("word01:"+word01); return "word01:"+word01; } @ResponseBody @RequestMapping(value = "test02", produces = "application/json; charset=UTF-8") public String test02(){ System.out.println("word02:"+word02); return "word02:"+word02; } }
访问:http://localhost:8080/test01
访问:http://localhost:8080/test02
注意,如果你用的也是IDEA的话,在springboot读取配置文件的环节,可能会使用Unicode读取,造成乱码;所以,要配置一下IDEA字符集:
这里需要额外说一下:我们在使用springboot的@RestController注解时,如果不对@RequestMapping的produces属性进行定义,则默认会返回text/html的Content-Type;
如果你的项目环境接口调试时要求必须是application/json的Content-Type,你可以像我上面的controller中写的那样定义produces属性;
上面说的是在字段中通过@value注解来获取配置文件的值,当你的配置文件中有n个互相关联的属性需要读取的时候,我们可以使用配置文件实体bean的方式将配置文件的相关值加载到bean中,再通过bean的属性来获取值:
我们由此,先新建配置bean,注意使用@ConfigurationProperties(prefix = "com.zjt"),来指定配置文件字段的前缀,bean的字段名要与配置文件属性名称完全一致:
@ConfigurationProperties(prefix = "com.zjt") public class ConfigBean01 { private String word01; private String word02; public String getWord01() { return word01; } public void setWord01(String word01) { this.word01 = word01; } public String getWord02() { return word02; } public void setWord02(String word02) { this.word02 = word02; } }
这样,我们就把配置文件的字段加载到了配置文件bean中,但要想使得该配置文件在IOC容器中注册bean,且让@ConfigurationProperties注解生效的话,还需要在main启动类中添加这样的注解:@EnableConfigurationProperties({ConfigBean01.class}),注意要指定配置文件的类名,如下:
@SpringBootApplication @EnableConfigurationProperties({ConfigBean01.class}) public class Chapter02Application { public static void main(String[] args) { SpringApplication.run(Chapter02Application.class, args); } }
这样在项目启动的时候会将配置文件类ConfigBean01.java的bean对象加载到IOC容器中,并使得ConfigBean01的@ConfigurationProperties注解有效,来读取配置文件的值,并绑定到字段中;
下面我们就可以在controller中调用配置文件bean了:
@RestController public class Controller01 { @Value(value="${com.zjt.welcomeWords01}") private String word01; @Value(value="${com.zjt.welcomeWords02}") private String word02; @Autowired private ConfigBean01 configBean01; @ResponseBody @RequestMapping("test01") public String test01(){ System.out.println("word01:"+word01); return "word01:"+word01; } @ResponseBody @RequestMapping(value = "test02", produces = "application/json; charset=UTF-8") public String test02(){ System.out.println("word02:"+word02); return "word02:"+word02; } @ResponseBody @RequestMapping(value = "test03", produces = "application/json; charset=UTF-8") public String test03(){ System.out.println("word01+word02:"+word01+word02); return "word01+word02:"+word01+word02; } }
访问:http://localhost:8080/test03
可以看到,我们已经很轻松的获取到了配置文件的值;
至此,我们已经可以很轻松的通过@value以及通过配置文件bean的方式来获取配置文件的值;
2.使用YAML代替Properties
Spring框架提供两个便利的类用于加载YAML文档,YamlPropertiesFactoryBean会将YAML作为Properties来加载,YamlMapFactoryBean会将YAML作为Map来加载。
示例:
environments:
dev:
url: http://dev.bar.com
name: Developer Setup
prod:
url: http://foo.bar.com
name: My Cool App
上面的YAML文档会被转化到下面的属性中:
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]间接引用作为属性keys的形式,例如下面的YAML:
my:
servers:
- dev.bar.com
- foo.bar.com
将会转化到下面的属性中:
my.servers[0]=dev.bar.com
my.servers[1]=foo.bar.com
使用Spring DataBinder工具绑定那样的属性(这是@ConfigurationProperties做的事),你需要确定目标bean中有个java.util.List或Set类型的属性,并且需要提供一个setter或使用可变的值初始化它,比如,下面的代码将绑定上面的属性:
@ConfigurationProperties(prefix="my") public class Config { private List<String> servers = new ArrayList<String>(); public List<String> getServers() { return this.servers; } }
注意:YAML文件不能通过@PropertySource注解加载。所以,在这种情况下,如果需要使用@PropertySource注解的方式加载值,那就要使用properties文件。
在了解了yaml之后,我们基于上面,在srcmain esourcesapplication.properties的平行位置新建application.yml,如下:
com:
zjt:
welcomeWords01: "你好,我是来自application.yml的com.zjt.welcomeWords01"
welcomeWords02: "你好,我是来自application.yml的com.zjt.welcomeWords02"
welcomeWords03: "你好,我是来自application.yml的com.zjt.welcomeWords03"
welcomeWords04: "你好,我是来自application.yml的com.zjt.welcomeWords04"
在controller中增加test04(),如下:
package com.zjt.chapter02.controller; import com.zjt.chapter02.bean.ConfigBean01; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; @RestController public class Controller01 { @Value(value="${com.zjt.welcomeWords01}") private String word01; @Value(value="${com.zjt.welcomeWords02}") private String word02; @Value(value="${com.zjt.welcomeWords03}") private String word03; @Value(value="${com.zjt.welcomeWords04}") private String word04; @Autowired private ConfigBean01 configBean01; @ResponseBody @RequestMapping("test01") public String test01(){ System.out.println("word01:"+word01); return "word01:"+word01; } @ResponseBody @RequestMapping(value = "test02", produces = "application/json; charset=UTF-8") public String test02(){ System.out.println("word02:"+word02); return "word02:"+word02; } @ResponseBody @RequestMapping(value = "test03", produces = "application/json; charset=UTF-8") public String test03(){ System.out.println("word01+word02:"+word01+word02); return "word01+word02:"+word01+word02; } @ResponseBody @RequestMapping(value = "test04", produces = "application/json; charset=UTF-8") public String test04(){ System.out.println("word03+word04:"+word03+word04); return "word03+word04:"+word03+word04; } }
注意,此时我们的项目中同时包含application.properties和application.yml,
我们分别访问:
http://localhost:8080/test03
http://localhost:8080/test04
由此我们初步得出:
1.当application.properties和application.yml处于同一级别目录时,application.properties中的属性会覆盖application.yml的属性值;
2.application.properties和application.yml两种配置文件可以同时存在;并均会被springboot加载;
3.springboot外化配置覆盖顺序
Spring Boot使用一个非常特别的PropertySource次序来允许对值进行合理的覆盖,需要以下面的次序考虑属性:
1. 命令行参数
2. 来自于java:comp/env的JNDI属性
3. Java系统属性(System.getProperties())
4. 操作系统环境变量
5. 只有在random.*里包含的属性会产生一个RandomValuePropertySource
6. 在打包的jar外的应用程序配置文件(application.properties,包含YAML和profile变量)
7. 在打包的jar内的应用程序配置文件(application.properties,包含YAML和profile变量)
8. 在@Configuration类上的@PropertySource注解
9. 默认属性(使用SpringApplication.setDefaultProperties指定)
比如,我们将上面的项目代码打包,mvn package;
得到:chapter02-0.0.1-SNAPSHOT.jar,这样我们其实已经将上文中的两个配置文件application.properties和application.yml同时打入了jar中;
这时,我们在该jar外同级位置,新建application.yml,
com:
zjt:
welcomeWords01: "你好,我是来自外部的application.yml的com.zjt.welcomeWords01"
welcomeWords02: "你好,我是来自外部的application.yml的com.zjt.welcomeWords02"
welcomeWords03: "你好,我是来自外部的application.yml的com.zjt.welcomeWords03"
welcomeWords04: "你好,我是来自外部的application.yml的com.zjt.welcomeWords04"
如图:
启动jar文件之后,访问:
http://localhost:8080/test03
http://localhost:8080/test04
上述现象证明:在打包的jar外的应用程序配置文件会覆盖在打包的jar内的应用程序配置文件;
除了使用外部配置文件覆盖以外,还可以通过命令附带参数来进行属性值的覆盖:
我们使用 java -jar chapter02-0.0.1-SNAPSHOT.jar --com.zjt.welcomeWords04="我是来自命令行的com.zjt.welcomeWords04"
重新启动后访问
http://localhost:8080/test04
所以,当我们在进行项目开发时,你可以将一个application.properties文件捆绑到jar内,用来提供一个合理的默认name属性值。
当运行在生产环境时,可以在jar外提供一个application.properties或yml文件来覆盖name属性。
对于一次性的测试,你可以使用特定的命令行开关启动(比如,java -jar app.jar --name="Spring")。
配置文件的优先级
SpringApplication将从以下位置加载application.properties文件,并把它们添加到Spring Environment中:
1. 当前目录下的一个/config子目录
2. 当前目录
3. 一个classpath下的/config包
4. classpath根路径(root)
这个列表是按优先级排序的(列表中位置高的将覆盖位置低的)。也就是说,src/main/resources/config下application.properties覆盖src/main/resources下application.properties中相同的属性;(注:你可以使用YAML('.yml')文件替代'.properties'。)
当application.properties和application.yml处于同一级别目录时,application.properties中的属性会覆盖application.yml的属性值;(我们在第2段已经证明了这一点)
如果不喜欢将application.properties作为配置文件名,你可以通过指定spring.config.name环境属性来切换其他的名称。你也 可以使用spring.config.location环境属性来引用一个明确的路径(目录位置或文件路径列表以逗号分割)。 $ java -jar myproject.jar --spring.config.name=myproject //or $ java -jar myproject.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties 如果spring.config.location包含目录(相对于文件),那它们应该以/结尾(在加载前,spring.config.name产生的名称将被追 加到后面)。不管spring.config.location是什么值,默认的搜索路径classpath:,classpath:/config,file:,file:config/总会被使用。 以这种方式,你可以在application.properties中为应用设置默认值,然后在运行的时候使用不同的文件覆盖它,同时保留默认 配置。 注:如果你使用环境变量而不是系统配置,大多数操作系统不允许以句号分割(period-separated)的key名称,但你可以使 用下划线(underscores)代替(比如,使用SPRING_CONFIG_NAME代替spring.config.name)。如果你的应用运行在一 个容器中,那么JNDI属性(java:comp/env)或servlet上下文初始化参数可以用来取代环境变量或系统属性,当然也可以使 用环境变量或系统属性。
4.配置随机值
RandomValuePropertySource在注入随机值(比如,密钥或测试用例)时很有用。它能产生整数,longs或字符串,比如:
my.secret=${random.value}
my.number=${random.int}
my.bignumber=${random.long}
my.number.less.than.ten=${random.int(10)}
my.number.in.range=${random.int[1024,65536]}
random.int*语法是OPEN value (,max) CLOSE,此处OPEN,CLOSE可以是任何字符,并且value,max是整数。如果提供
max,那么value是最小的值,max是最大的值(不包含在内)。
5.Profile-多环境配置
当我们在生产环境和开发环境使用不同的数据库时可以将数据库连接配置到两个不同的配置环境中;
Spring Profiles提供了一种隔离应用程序配置的方式,并让这些配置只能在特定的环境下生效。任何@Component或@Configuration都能被@Profile标记,从而限制加载它的时机; springboot通过使用spring.profiles.active属性来设置不同的项目环境使用的配置文件;
如:我们在application.properties相同级别位置下新建两个配置文件,application-dev.properties,application-prod.properties分别对应开发环境配置文件和生产环境配置文件;
在application.properties中添加配置:
你也可以在命令行开关中携带该属性值:
--spring.profiles.active=dev
这样,我们重启项目:
访问:http://localhost:8080/test03
当然你也可以用命令行启动的时候带上参数:
java -jar manager.jar --spring.profiles.active=prod
或
java -jar manager.jar --spring.profiles.active=dev
添加激活的配置(profiles)
spring.profiles.active属性和其他属性一样都遵循相同的排列规则,最高的PropertySource获胜。也就是说,你可以在application.properties中指定生效的配置,然后使用命令行开关替换它们。
有时,将特定的配置属性添加到生效的配置中而不是替换它们是有用的。spring.profiles.include属性可以用来无条件的添加生效的配置。SpringApplication的入口点也提供了一个用于设置额外配置的Java API(比如,在那些通过spring.profiles.active属性生效的配置之上):参考setAdditionalProfiles()方法。
示例:当一个应用使用下面的属性,并用 --spring.profiles.active=prod 开关运行,那proddb和prodmq配置也会生效:
---
my.property: fromyamlfile
---
spring.profiles: prod
spring.profiles.include: proddb,prodmq
注:spring.profiles属性可以定义到一个YAML文档中,用于决定什么时候该文档被包含进配置中。
Multi-profile YAML
你可以在单个文件中定义多个特定配置(profile-specific)的YAML文档,并通过一个spring.profiles key标示应用的文档。例如:
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配置被激活,那server.address属性将是127.0.0.1。如果development和production配置(profiles)没有启用,则该属性的值将是192.168.1.100。
以编程方式设置profiles
在应用运行前,你可以通过调用SpringApplication.setAdditionalProfiles(…)方法,以编程的方式设置生效的配置。使用Spring的ConfigurableEnvironment接口激动配置也是可行的。
在代码里,我们还可以直接用@Profile注解来进行配置,例如数据库配置,这里我们先定义一个接口:
分别定义俩个实现类来实现它
通过在配置文件激活具体使用哪个实现类
然后就可以这么用了