看过前几篇系列文章的人,应该会对“application.properties”字眼比较熟悉。其实这就牵引出了Spring Boot外部化属性配置的内容。
properties文件在Java中一直充当着属性配置的角色,我们可以将应用中涉及配置方面的键值对内容放入properties文件中,方便我们在一个地方集中地管理配置项。
Spring Boot总会帮我们考虑的实践中可能常用的技巧,所以在Spring Boot应用中使用属性配置会更加轻松上手。Spring Boot外部化属性配置主要有两方面的体现:
- 针对Spring Boot特性及自动配置的控制。比如之前我们提到的数据源的配置、日志文件的配置等等。
- 自定义属性的配置及引用。
属性加载原则
Spring Boot应用中,属性的来源可能很多,针对不同来源的属性,会有优先级顺序。同名情况下,优先级高的会覆盖优先级低的。如下:
不同次序的载入,可以扩展使用的灵活度。比如针对一次性的测试,你可以用命令行的方式追加属性配置,而无需再去修改配置文件。
Spring Boot的默认配置
看到上面的加载原则,又见到了“application.properties”的身影。没错,以往我们的属性配置文件都是自定义并指定载入的。Spring Boot默认为我们提供了一个配置之地!只要按照指定的名称创建属性文件,并放置在指定位置,Spring Boot会自动解析其内容。
默认情况下Spring Boot会从以下位置寻找application.properties文件,并把属性加入到Spring Environment中。
- 当前目录下的一个/config子目录
- 当前目录
- 一个classpath下的/config包
- classpath根路径(root)
上述列表中的位置优先级自上而下递减,同样的属性配置,高位置的会覆盖低位置的!
假如你不喜欢“application.properties”这个默认文件名,你可以重新设定:
- spring.config.name属性直接指定属性文件名称。
- spring.config.location属性指定明确路径。
**ps:**例如spring.config.location=classpath:mine.properties,classpath:config/mine.properties,多个文件路径以逗号间隔。如果路径以最后为目录,必须以"/"结尾。例如spring.config.location=classpath:myconfig/,那么spring.config.name会自动追加到后面!一旦设定了spring.config.location属性,上述的默认位置将不起作用!
因为你要修改的是application.properties的名称,所以上面的两个属性建议通过命令行的方式配置,如:java -jar xxx.jar --spring.config.name=mine.properties。当然你还可以通过环境变量等方式,请参考上面的默认加载原则。
同理,上述的默认配置规则适用于YAML方式(后面会详述)。表面区别只在于,将“.properties”改为“.yml”即可。
命令行属性
默认情况下,Spring Boot应用会将命令行任何可选的参数(以“--”开头,如--server.port=90)转化为property并添加到Spring environment中。
如果你想禁止这一行为,可使用代码:SpringApplication.setAddCommandLineProperties(false)
。
属性的配置方式
Spring Boot提供了两种属性配置方式,一种是传统的properties文件,一种是YAML。后者笔者也是略知一二,并无实践经验。
properties方式
这种方式自然需要properties文件,适合于键值对形式的属性配置。
properties文件中可以使用占位符,引用之前定义的其他属性值!例如:
app.name=MyApp
app.description=${app.name} is a Spring Boot application
YAML方式
YAML是Json的一个超集,更适合于具有层次结构形式的属性配置。你只要将SnakeYAML库放在classpath下,SpringApplication类会自动将YAML转化为properties属性。如果你使用了“Starter POMs”,会自动提供SnakeYAML。YAML学习入口
YAML文档以“.yml”结尾,格式如下:
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列表
YAML还有一种列表格式,如下:
my:
servers:
- dev.bar.com
- foo.bar.com
转化之后呢,变为:
my.servers[0]=dev.bar.com
my.servers[1]=foo.bar.com
无论是properties方式还是YAML方式,都可以通过传统的@Value("${property}")注解的方式引用属性值。
属性配置beans
有时你会觉着@Value("${property}")注解引用属性值会很笨重,特别是需要使用多个properties或者使用的属性具有层次结构。
Spring Boot允许你将属性值绑定到强类型的beans上面。需要两个条件:
- 定义属性bean,使用@ConfigurationProperties注解,bean的属性名称与properties匹配,并提供对应的setter方法。
- @Configuration类上需要使用@EnableConfigurationProperties注解,那么任何被@ConfigurationProperties注解的beans将自动被Environment属性配置。
下面是一段代码示例:
@Component
@ConfigurationProperties(prefix="connection")
public class ConnectionSettings {
private String username;
private InetAddress remoteAddress;
// ... getters and setters
}
其实这种风格特别适合于YAML方式配合使用。下面是对应的yml文档内容:
# application.yml
connection:
username: admin
remoteAddress: 192.168.1.1
针对YAML列表格式的属性,绑定到bean上有几点需要注意:
- bean中必须有个java.util.List或Set类型的属性。
- 除了setter方法初始化属性外,还可以可变的值初始化属性。(笔者认为setter更直观常用)
下面是列表格式的示例:
@ConfigurationProperties(prefix="my")
public class Config {
private List<String> servers = new ArrayList<String>();
public List<String> getServers() {
return this.servers;
}
}
你可以使用注入的方式来使用属性bean,如下:
@Service
public class MyService {
@Autowired
private ConnectionSettings connection;
//...
}
}
同样地,属性还可以绑定到第三方的组件bean上,只要符合上面提到的两个条件,@ConfigurationProperties应用到@Configuration类中的@Bean上即可。如下:
@ConfigurationProperties(prefix = "foo")
@Bean
public FooComponent fooComponent() {
...
}
属性bean的方式还有一个优势,你可以@ConfigurationProperties类添加JSR-303 javax.validation约束注解,例如:
@Component
@ConfigurationProperties(prefix="connection")
public class ConnectionSettings {
@NotNull
private InetAddress remoteAddress;
// ... getters and setters
}
松散的绑定
上面提到属性绑定bean时,bean的属性名称应该与properties匹配。其实这方面Spring Boot提供了一种松散的机制,不需要精确匹配。像操作系统的环境变量不允许使用句号却可以使用下划线分隔,Spring Boot提供了很好的转化。规则如下:
- 标准驼峰格式:person.firstName
- 虚线格式(推荐.properties和.yml文件中):person.first-name
- 大写形式(推荐系统环境变量使用):PERSON_FIRST_NAME
上面的三种格式,都会绑定到下面bean中的firstName属性上。
@Component
@ConfigurationProperties(prefix="person")
public class ConnectionSettings {
private String firstName;
}