文章目录
Spring Boot 外部化配置 - 上篇
文章说明
本系列会完整的介绍 Spring Boot 中外部化配置相关应用以及部分源码分析
- 上篇 - 什么是外部化配置,外部化配置有哪些应用,顺序覆盖性
- 中篇 - @Value 注入、Environment 抽象、@ConfigurationProperties、@ConditionalOnProperty 应用
- 下篇 - 如何扩展外部化配置,代码演示覆盖顺序
项目环境
- jdk 1.8
- Spring Boot 2.0.2.RELEASE
- github 地址:https://github.com/huajiexiewenfeng/deep-in-spring-boot
- 本章模块:externalized-configuration
1.Spring Boot 官方说明
24. Externalized Configuration
Spring Boot lets you externalize your configuration so that you can work with the same application code in different environments. You can use properties files, YAML files, environment variables, and command-line arguments to externalize configuration. Property values can be injected directly into your beans by using the
@Value
annotation, accessed through Spring’sEnvironment
abstraction, or be bound to structured objects through@ConfigurationProperties
.
翻译:Spring Boot 让你可以外部化你的配置,以便于你可以让相同的应用在不同的环境中可以正常工作。你可以使用 properties、YAML 、环境变量和命令行参数去外部化配置,这里还举了三个使用方式
@Value
注解Environment
抽象@ConfigurationProperties
2.举例说明
这里我们可以举一个简单的例子比如
使用 application.properties 去调整 Tomcat 的端口号
通常我们的默认端口是 8080,但是为了防止端口冲突,通常我们会修改 application.properties 文件增加如下配置即可,表示应用以 8085 端口启动,application.properties 文件并非程序代码部分,所以称为外部化配置。
server.port = 8085
小马哥(《Spring Boot 编程思想》作者)解读
何为
外部化配置
?通常,对于可扩展性应用,尤其是中间件,他们的功能性组件是可配置化的,如:认证信息、端口范围、线程池规模以及连接时间等等。假设需要设置 Spring 应用的
Profile
为dev
,可通过调整 SpringConfigurableEnvironment
的setActiveProfiles("dev")
方法实现。这种方式是一种显示的代码配置,配置数据来源于应用内部实现,所以称为内部化配置
,虽然能达成目的,但是缺少相应的弹性。那么相对应的就是
外部化配置
,比如:application.properties 加入如下配置spring.profiles.active = dev
3.应用外部化配置
本章只介绍 XML Bean 这种方式,其他的 4 种在
Spring Boot 外部化配置 - 中篇
进行详细讨论
应用场景
- XML Bean 定义的属性占位符
- @Value 注入 - @since SpringFramework 3.0
- Environment 读取 - @since SpringFramework 3.1
- @ConfigurationProperties 绑定(Spring Boot 中新增注解)
- @ConditionalOnProperty 判断(Spring Boot 中新增注解)
4 XML Bean 定义的属性占位符
4.1 传统 spring 例子
在 resources/META-INF/spring 下新建 spring-context.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 属性占位符配置-->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><!-- Properties 文件 classpath 路径 -->
<property name="location" value="classpath:/META-INF/default.properties"/><!-- 文件字符编码 -->
<property name="fileEncoding" value="UTF-8"/>
</bean>
</beans>
user-context.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- User Bean -->
<bean id="user" class="com.huajie.deepinspringboot.externlized.configuration.domain.User">
<property name="id" value="${user.id}"/>
<property name="name" value="${user.name}"/>
</bean>
</beans>
在 resources/META-INF 下创建
default.properties
user.id = 1
user.name = 小仙
新建 User 类
public class User {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
引导类 SpringXmlConfigPlaceHolderBootstrap
public class SpringXmlConfigPlaceHolderBootstrap {
public static void main(String[] args) {
String[] locations = new String[]{"META-INF/spring/spring-context.xml", "META-INF/spring/user-context.xml"};
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(locations);
User user = applicationContext.getBean("user",User.class);
System.out.println(user);
applicationContext.close();
}
}
执行结果
User{id=1, name='小仙'}
4.2 Spring Boot 例子
修改 spring-context.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 属性占位符配置-->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="fileEncoding" value="UTF-8"/>
<!-- 替换为 Spring Boot 中的 application.properties -->
<property name="location" value="classpath:application.properties"/>
</bean>
</beans>
resources 目录下新增 application.properties 文件
user.id = 1
user.name = 小仙2020
引导类
@ImportResource("META-INF/spring/user-context.xml")
@EnableAutoConfiguration
public class XmlPlaceHolderExternalizedConfigurationBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder(XmlPlaceHolderExternalizedConfigurationBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
User user = applicationContext.getBean("user",User.class);
System.out.println(user);
applicationContext.close();
}
}
执行结果:
User{id=1, name='Administrator'}
结果并非我们配置的 小仙2020
,而是 Administrator
,问题出在哪里呢?
改造上面的引导类,
@ImportResource("META-INF/spring/user-context.xml")
@EnableAutoConfiguration
public class XmlPlaceHolderExternalizedConfigurationBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder(XmlPlaceHolderExternalizedConfigurationBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
User user = applicationContext.getBean("user",User.class);
System.out.println(user);
System.out.println("系统变量:"+System.getProperty("user.name"));
applicationContext.close();
}
}
执行结果:
User{id=1, name='Administrator'}
系统变量:Administrator
其实打印的 user.name
属性是 Java 系统变量,而并不是我们预期的 application. properties 文件中配置的属性。
4.3 外部化配置顺序覆盖问题
这里我们可以看 Spring Boot 官方文档
24. Externalized Configuration
Spring Boot uses a very particular
PropertySource
order that is designed to allow sensible overriding of values. Properties are considered in the following order:
- Devtools global settings properties on your home directory (
~/.spring-boot-devtools.properties
when devtools is active).@TestPropertySource
annotations on your tests.@SpringBootTest#properties
annotation attribute on your tests.- Command line arguments.
- Properties from
SPRING_APPLICATION_JSON
(inline JSON embedded in an environment variable or system property).ServletConfig
init parameters.ServletContext
init parameters.- JNDI attributes from
java:comp/env
.- Java System properties (
System.getProperties()
).- OS environment variables.
- A
RandomValuePropertySource
that has properties only inrandom.*
.- Profile-specific application properties outside of your packaged jar (
application-{profile}.properties
and YAML variants).- Profile-specific application properties packaged inside your jar (
application-{profile}.properties
and YAML variants).- Application properties outside of your packaged jar (
application.properties
and YAML variants).- Application properties packaged inside your jar (
application.properties
and YAML variants).@PropertySource
annotations on your@Configuration
classes.- Default properties (specified by setting
SpringApplication.setDefaultProperties
).
外部化配置有个顺序的覆盖问题,前面的配置属性可以覆盖后面配置属性。
我们的 application.properties 文件排名 13,而 Java System properties 的顺序是 9,所以 Java 系统变量 user.name
覆盖了我们 application.properties 中配置的属性。
为了验证这个顺序的覆盖性,我们再测试一种配置方式 4.Command line arguments.
在启动程序中配置命令行的参数--user.name=xwf
启动执行结果:
User{id=1, name='xwf'}
系统变量:Administrator
可以看到输出的 User 对象 name 属性变成了 xwf
,验证了我们的猜想。
5.参考
- 慕课网-小马哥《Spring Boot2.0深度实践之核心技术篇》