Spring Boot中的配置

这里所说的配置是指properties文件这样的配置



配置的方式及优先级

Spring Boot允许通过properties文件, YAML文件, Environment变量, 命令行参数等进行配置. 属性值可以通过@Value注入到bean中并通过Spring的Environment访问, 或通过@ConfigurationProperties直接绑定到对象上.

Spring Boot所提供的配置优先级从高到低如下所示:

  1. Spring的devtools的全局配置(~/.spring-boot-devtools.properties文件)(当使用了devtools时)
  2. Test类上通过@TestPropertySource声明的属性文件
  3. Test类上通过@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包外的profile配置文件(application-{profile}.propertiesYAML配置)
  13. jar包内的profile配置文件(application-{profile}.propertiesYAML配置)
  14. jar包外的应用程序配置文件(application.propertiesYAML配置)
  15. jar包内的应用程序配置文件(application.propertiesYAML配置)
  16. 配置类(@Configuration类)上的通过@PropertySource注解声明的属性文件
  17. 通过SpringApplication.setDefaultProperties声明的默认属性

优先级举例

classpath:application.properties文件里有个name变量(假设将它打成了jar包), 当在一个新的环境中运行时, 可以通过在jar包外(即新环境的的classpath下)提供一个application.properties文件, 重新设置name变量的值. 甚至在测试的时候,可以通过优先级更高的命令行参数指定name的值(java -jar app.jar --name="Spring")

命令行参数

SpringApplication会把所有的命令行参数(以--开头, 如--server.port=9000)转化为属性加载到Spring的Environment中, 命令行参数的优先级高于配置文件

如果不想让命令行参数添加到Environment中, 可通过SpringApplication.setAddCommandLineProperties(false)设置

SPRING_APPLICATION_JSON

上面第5条中说的SPRING_APPLICATION_JSON属性, 可以在命令行中指定

$ SPRING_APPLICATION_JSON='{"foo":{"bar":"spam"}}' java -jar myapp.jar  // 环境变量形式

这样就相当于在Spring的Environment中添加了foo.bar=spam.
也可以像下面这些方式提供:

$ 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提供

其实上面介绍的这几条优先级比较高的配置, 实际并不太常用. 命令行在测试的时候用的还算比较多


配置文件:application.properties

SpringApplication默认会加载配置文件application.properties中的配置并加到Spring Environment中, 该文件的加载有个优先级: classpath:/config/application.properties > classpath:/application.properties即在classpath:/config/下的配置文件优先级比较高. 也可以使用YAML文件(application.yml)来替代properties文件.

application.properties被称为Spring Boot的外露配置, 文件中有很多属性可用来配置整个应用, 比如server.port=8080等; 你可以通过指定这些属性值来配置应用.

配置文件的名字和位置, 也可自定义, 可通过spring.config.namespring.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.location指定的是一个目录, 则应该以/结尾, 并且使用该目录下spring.config.name指定的配置文件

随机变量

RandomValuePropertySource可以注入一些随机变量, 可产生integer, long, string, 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, OPEN,CLOSE是字符, value,max是整数. 如果有max则最小值是value最大值是max(不包括max).

变量引用

application.properties中定义的变量已经被Environment过滤, 所以可以引用前面定义过的变量, 比如:

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

多环境配置

  • 从配置的优先级的第12~15条可以看出, application-{profile}.properties的优先级要高于application.properties.
  • 这个profile就用于区分是dev环境还是beta环境还是prod环境. 如果没有被指定, 默认会使用application-default.properties配置.
  • 至于到底启用哪个profile, 可以在application.properties中通过属性spring.profiles.active=profile来指定, 在profile配置文件中指定该属性不起作用.

举个例子, application.properties中有个默认属性server.port=8080用于指定服务的端口. 假设有下面的文件, 文件内容如下:

// application.properties
server.port=8080
spring.profiles.active=dev
// application-default.properties
server.port=8081
// application-dev.properties
server.port=8082
// application-prod.properties
server.port=8083

假设application.properties中不指定spring.profiles.active属性, 则application-default.properties中的8081端口生效, 若指定spring.profiles.active=prod, 则8083端口生效. 访问8080端口都会找不到服务


自定义配置

Spring Boot默认加载application.properties中的配置, 这个文件中的默认属性相当多…
如果我们要加载自己的配置, 比如下面的数据库配置:

db.driver=MySQL
db.username=username
db.password=123456
db.tables[0]=table1
db.tables[1]=table2

可以把这些属性直接放到application.properties中, 但极力不推荐这样.

传统的配置加载方式

我们一般都是定义自己的配置文件, 比如把这些属性放到db.properties文件. 然后通过@PropertySource加载配置文件, 然后通过@Value("${key:defaultVlaue}")的形式进行配置, 如下:

@Component
@PropertySource("db.properties")
public class DBConfig {
    @Value("${db.driver}")
    private String driver;

    @Value("${db.username}")
    private String username;

    @Value("${db.password}")
    private String password;

    @Value("${db.tables[0]}")
    private String table1;

    @Value("${db.tables[1]}")
    private String table2;

}

注: properties文件默认是按照unicode加载, 若有中文, 一定要指定编码@PropertySource(value = "db.properties", encoding = "UTF-8")

类型安全的配置加载方式

上面这种方式在Spring Framework普遍使用, 但是 Spring Boot提供了更高级的使用配置的方式,类似于Spring中的DataBinder工具. 还是db.properties文件, 我们可以这样进行数据绑定:


@Data
@Component
@ConfigurationProperties(prefix="db", locations = "classpath:db.properties")
public class DBConfig {
    private String driver;
    private String username;
    private String password;
    private List<String> tables;
}

最上面的@DataLombok包中用于生成getter, setter等的注解, pom依赖为:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.10</version>
</dependency>

不用这个包也可以, 那就需要自己写 gettersetter方法了

另外注意此时该类上是加了@Component注解的, 这样才会被当作Spring的Bean.

其实不在DBConfig上加@Component注解也有办法, 通常@ConfigurationProperties是和@EnableConfigurationProperties一起使用的, @EnableConfigurationProperties注解需要加到配置类上. 像下面这样使用:

// 配置类
@SpringBootApplication
@EnableConfigurationProperties({DBConfig.class})
public class Application {
  // 代码
}
// 加载属性的类(主意这个类没有加 @Component 注解)
@ConfigurationProperties(prefix="db", locations = "classpath:db.properties")
public class DBConfig {
  // 代码
}

这种形式, @ConfigurationPropertiesbean将会以名字<prefix>-<fqn>注册, <prefix>就是注解中指定的前缀, <fqn>是该类的全类名. 上面的DBConfig将会被注册成名字为db-com.example.myproject.config.DBConfig的bean

@ConfigurationProperties的优缺点

优点:

  • 结构化, 对于结构化的配置, 优势明显
  • 松散绑定, Environment属性名和@ConfigurationProperties Beans属性名不需要精确匹配, 比如驼峰person.firstName, 虚线pserson.first-name, 下划线person.first_name, 大写PERSON_FIRST_NAME都能正确区分绑定
  • 可校验, 可以在属性上添加@NotNull, @NotEmpty等(JSR-303)注解进行校验
  • 可生成meta-data文件(可被IDE使用)

缺点:

  • 不支持SpEL表达式

使用YAML配置

YAMLJSON的超集, 有一定的结构, SpringApplication提供了对YAML的支持. 使用YAML配置文件需要确保在classpath中引入了SnakeYAML包, spring-boot-starter中已经包含了SnakeYAML包, 也可以主动显式地添加pom依赖:

<dependency>
	  <groupId>org.yaml</groupId>
		<artifactId>snakeyaml</artifactId>
		<version>1.17</version>
</dependency>

加载application.yml

Spring Boot会自动加载这个配置, 因此效果跟application.properties一样。
Spring 提供了两个方便的类加载YAML, YamlPropertiesFactoryBeanYAML作为Properties加载, YamlMapFactoryBeanYAML作为Map加载;
YamlPropertySourceLoader可以把YAML作为PropertySource加到Spring Environment中, 这样就可以用@Value的方式进行注入了.

比如下面的写法是一样的

// yml文件
environments:
    dev:
        url: http://dev.bar.com
        name: Developer Setup
    prod:
        url: http://foo.bar.com
        name: My Cool App
my:
    servers:
        - dev.bar.com
        - foo.bar.com
// properties文件
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
my.servers[0]=dev.bar.com
my.servers[1]=foo.bar.com

加载自定义YAML配置

遗憾的是, YAML不能像properties文件一样使用@PropertySource注解的方式加载.
加载自定义的YAML文件可以通过@ConfigurationProperties注解来加载, 如:@ConfigurationProperties(prefix="db", locations = "classpath:db.yml")