SpringBoot Profiles特性
今天我们了解SpringBoot Profiles特性
一、外部化配置
配置分为编译时和运行时,而Spring采用后者,在工作中有时也会两者一起使用。
何为“外部化配置”官方没有正面解释。通常,对于可扩展性应用,尤其是中间件,它们的功能性组件是可配置化的,如线程池配置及数据库连接信息等。
假设设置Spring应用的Profile为dev,通过 ConfigurableEnvironment#setDefaultProfiles 方法实现,这种通过代码的方式配置,配置数据来源于应用内部实现的称为“内部化配置”。
SpringBoot内置了17种外部化配置,并规定了其调用顺序。实际不止17种,也并不是必须按官方规定的顺序。
官方说明:
4.2. 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
@Valueannotation, accessed through Spring’sEnvironmentabstraction, or be bound to structured objects through@ConfigurationProperties.Spring Boot uses a very particular
PropertySourceorder that is designed to allow sensible overriding of values. Properties are considered in the following order:
- Devtools global settings properties in the
$HOME/.config/spring-bootfolder when devtools is active.@TestPropertySourceannotations on your tests.propertiesattribute on your tests. Available on@SpringBootTestand the test annotations for testing a particular slice of your application.- Command line arguments.
- Properties from
SPRING_APPLICATION_JSON(inline JSON embedded in an environment variable or system property).ServletConfiginit parameters.ServletContextinit parameters.- JNDI attributes from
java:comp/env.- Java System properties (
System.getProperties()).- OS environment variables.
- A
RandomValuePropertySourcethat has properties only inrandom.*.- Profile-specific application properties outside of your packaged jar (
application-{profile}.propertiesand YAML variants).- Profile-specific application properties packaged inside your jar (
application-{profile}.propertiesand YAML variants).- Application properties outside of your packaged jar (
application.propertiesand YAML variants).- Application properties packaged inside your jar (
application.propertiesand YAML variants).@PropertySourceannotations on your@Configurationclasses.- Default properties (specified by setting
SpringApplication.setDefaultProperties).
配置的引用方式
1)XML 文件
根据spring规范,元信息存放在META-INF目录下。
示例: 在resources目录下创建META-INF/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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="xmlPerson" class="com.example.profiledemo.property.XmlPerson">
<property name="name" value="xml name" />
<property name="age" value="10" />
</bean>
</beans>
定义JavaBean
/*
* @auth yuesf
* @data 2019/11/23
*/
public class XmlPerson {
private String name;
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
使用xml配置属性
/*
* @auth yuesf
* @data 2019/11/23
*/
@RestController
@ImportResource(locations = { "META-INF/spring/context.xml" })
public class ContextController {
@Autowired
private XmlPerson xmlPerson;
@GetMapping("/xml")
public XmlPerson xml() {
return xmlPerson;
}
}
启动服务运行结果如下
{
"name": "xml name",
"age": "10"
}
2)Annotation
官方提供两种方式 @Value、@ConfigurationProperties
(1)@Value
@Value是绑定application配置文件的属性变量
示例:
applicaton.properties文件配置
person.name=yuesf
使用Annotation配置属性
@Value("${person.name:defaultValue}")
private String name;
@Value在Spring中是强校验,使用时必须在配置中存在,否则会无法启动,示例中采用容错的方式,不存在使用默认值。
@Value的语义可以参考java.util.Properties#getProperty(java.lang.String, java.lang.String)方法, 如果变量存在,则取变量值,若不存在取默认值
(2)@ConfigurationProperties
官方说明:
4.2.8. Type-safe Configuration Properties
Using the
@Value("${property}")annotation to inject configuration properties can sometimes be cumbersome, especially if you are working with multiple properties or your data is hierarchical in nature. Spring Boot provides an alternative method of working with properties that lets strongly typed beans govern and validate the configuration of your application.
使用@Value来表达多个属性时特别麻烦,官方说明使用与JavaBean绑定的方式联合使用,使用方式如下:
使用@ConfigurationProperties 需要两步完成使用
- 必须要定义一个类来与属性做绑定。
示例说明:
/*
* @auth yuesf
* @data 2019/11/22
*/
@ConfigurationProperties("person")
public class Person {
private String name;
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
- 使用@EnableConfigurationProperties 激活Person配置
示例说明:
@SpringBootApplication
@EnableConfigurationProperties(Person.class)
public class ProfileDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ProfileDemoApplication.class, args);
}
}
3)Java Code (硬编码)
(1) 实现EnvironmentAware
示例通过实现EnvironmentAware 接口来自定义server.port端口号为7070:
/*
* @auth yuesf
* @data 2019/11/26
*/
@Component
public class CustomizedEnvironment implements EnvironmentAware {
@Override
public void setEnvironment(Environment environment) {
System.out.println("当前激活profile文件是:"+Arrays.asList(environment.getActiveProfiles()));
if(environment instanceof ConfigurableEnvironment){
ConfigurableEnvironment env = ConfigurableEnvironment.class.cast(environment);
MutablePropertySources propertySources = env.getPropertySources();
Map<String, Object> source = new HashMap<>();
source.put("server.port","7070");
PropertySource propertySource = new MapPropertySource("javacode",source);
propertySources.addFirst(propertySource);
}
}
}
启动后验证端口号未发生变更,不是我们想要的效果
...
The following profiles are active: dev
2019-11-26 18:04:26.850 INFO 54924 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
...
当前激活profile文件是:[dev]
...
通过actuator查看端口号已经变更
http://127.0.0.1:8080/actuator/env/server.port
"propertySources":
[
{
"name":"server.ports"
},
{
"name":"javacode",
"property":{
"value":"7070"
}
},
{
"name": "commandLineArgs"
},
...
]
问题:
这里会遇到一个问题,请问为什么这里的7070端口号没有使用呢?
文中
javacode是我们代码中指定的名称。propertySources的取值逻辑是顺序读取,一但有值就会返回。而返回后又对propertySources做了addFirst操作,所以会造成相互覆盖。源码地址: org.springframework.core.env.PropertySourcesPropertyResolver#getProperty(java.lang.String, java.lang.Class, boolean)
要想使用改后的属性,我们可以仿照源码使用下面这种自定义事件ApplicationListener 的方式。
(2)自定义事件ApplicationEnvironmentPreparedEvent
/*
* @auth yuesf
* @data 2019/11/23
*/
public class CustomizedSpringBootApplicationListener
implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment env = event.getEnvironment();
MutablePropertySources propertySources = env.getPropertySources();
Map<String, Object> source = new HashMap<>();
source.put("server.port","6060");
PropertySource propertySource = new MapPropertySource("customizedListener",source);
propertySources.addFirst(propertySource);
}
}
添加Spring SPI配置 META-INF/spring.factories
# Application Listeners
org.springframework.context.ApplicationListener=\
com.example.profiledemo.listener.CustomizedSpringBootApplicationListener
启动后验证结果,启动端口已经生效
...
The following profiles are active: dev
Tomcat initialized with port(s): 6060 (http)
...
当前激活profile文件是:[dev]
...
通过actuator查看端口号,发现7070为第一个,6060为第二个。
"propertySources":
[
{
"name":"server.ports"
},
{
"name":"javacode",
"property":{
"value":"7070"
}
},
{
"name":"customizedListener",
"property":{
"value":"6060"
}
},
{
"name": "commandLineArgs"
},
...
]
根据结果猜测,这样结果虽然已经修改过来了,但由于后使用addFirst方法对顺序做了改动。把javacode 放在了第一位。
Profiles使用场景
1)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
https://www.springframework.org/schema/beans/spring-beans.xsd"
profile="test">
...
</beans>
spring中对xml做了属性封装,使用profile方式来加载,示例中使用的是profile="test"
2)Properties文件
properties文件名按 application-{profile}.properties 规约来命名
3)Annotation使用
通过 @Profile 方式指定一个或多个 profile
4)命令行
通过--spring.profiles.active 命令行指定使用的profile,还可以使用 --spring.profiles.include引用多个profile
三、装配原理
通过上面说明并没有讲清楚他的装配原理是什么,那么我们通过源码了解下装配原理。
1.首先第一步是查看spring-boot源码#META-INF/spring.factories
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
PropertySourceLoader接口有两个实现
PropertiesPropertySourceLoader 解析properties和xml
public class PropertiesPropertySourceLoader implements PropertySourceLoader { private static final String XML_FILE_EXTENSION = ".xml"; @Override
public String[] getFileExtensions() {
return new String[] { "properties", "xml" };
}
...
}YamlPropertySourceLoader 解析 yml和yaml
public class YamlPropertySourceLoader implements PropertySourceLoader { @Override
public String[] getFileExtensions() {
return new String[] { "yml", "yaml" };
}
...
}
2.不管哪种解析,查下load方法是由谁来调用
本文使用idea查看代码,查看代码需要下载源码才可以查看。
本文中提到查看源码的方法调用统一使用idea自带的快捷键Alt+F7,或鼠标右键Find Usages
发现load方法是由ConfigFileApplicationListener.Loader#loadDocuments 方法调用。
再次查看ConfigFileApplicationListener 这个类是被谁调用,同样使用鼠标右键Find Usages ,发现会出来很多,那么我们会有选择性的查看。看哪一个呢?使劲找就能找到我们刚才看到的那个文件/META-INF/spring.factories 文件中有个ApplicationListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
查到这里就涉及到spring的事件,如果你不清楚Spring事件可以看下相关文档。
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}
ApplicationListener 只有一个方法 onApplicationEvent 同样查看刚才我们定位到spring.factories文件查看 ConfigFileApplicationListener#onApplicationEvent 方法,
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
这个方法非常简单,通过判断如果是 ApplicationEnvironmentPreparedEvent 类型时怎么做,意思是当应用环境准备时怎么做。第二个是如果是 ApplicationPreparedEvent类型怎么做,意思是应用准备时怎么做。
3.反过来在看下我们Java Code的方式 使用自定义事件时会生效的原因。
本次整个分析到此结束
本文由博客一文多发平台 OpenWrite 发布!
再次感谢!!! 您已看完全文,欢迎关注微信公众号猿码 ,你的支持是我持续更新文章的动力!
SpringBoot Profiles特性的更多相关文章
- SpringBoot Profiles 多环境配置及切换
目录 前言 默认环境配置 多环境配置 多环境切换 小结 前言 大部分情况下,我们开发的产品应用都会根据不同的目的,支持运行在不同的环境(Profile)下,比如: 开发环境(dev) 测试环境(tes ...
- SpringBoot的特性
SpringBoot的理念“习惯优于配置” 习惯优于配置(项目中存在大量的配置,此外还内置了一个习惯性的配置,无须手动进行配置) 使用SpringBoot可以方便地创建独立运行.准生产级别的基于Spr ...
- SpringBoot核心特性之组件自动装配
写在前面 spring boot能够根据依赖的jar包自动配置spring boot的应用,例如: 如果类路径中存在DispatcherServlet类,就会自动配置springMvc相关的Bean. ...
- 一文掌握 Spring Boot Profiles
Spring Boot Profiles 简介 Profile 的概念其实很早在 Spring Framework 就有了,在 Spring Framework 3.1 版本引入了注解 @Profil ...
- SpringBoot 正式环境必不可少的外部化配置
前言 <[源码解析]凭什么?spring boot 一个 jar 就能开发 web 项目> 中有读者反应: 部署后运维很不方便,比较修改一个 IP 配置,需要重新打包. 这一点我是深有体会 ...
- 助力SpringBoot自动配置的条件注解ConditionalOnXXX分析--SpringBoot源码(三)
注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 前言 本篇接 如何分析SpringBoot源码模块及结构?--SpringBoot源码(二) 上一篇分析了SpringBoo ...
- Spring-boot:5分钟整合Dubbo构建分布式服务
概述: Dubbo是Alibaba开源的分布式服务框架,它最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者最大限度地松耦合).从服务模型的角度来看,Dubbo采用的是一种非常 ...
- SpringBoot简介
Spring Boot,简单讲就是牺牲项目的自由度来减少配置的复杂度(“契约式编程”思想,SpringBoot自动配置方案的指导思想).约定一套规则,把这些框架都自动配置集成好,从而达到“开箱即用”. ...
- Java工程师之SpringBoot系列教程前言&目录
前言 与时俱进是每一个程序员都应该有的意识,当一个Java程序员在当代步遍布的时候,你就行该想到我能多学点什么.可观的是后端的框架是稳定的,它们能够维持更久的时间在应用中,而不用担心技术的更新换代.但 ...
随机推荐
- java基础之 变量
变量是一个内存位置的名称. 1.成员变量(实例变量,属性) 成员变量就是类中的属性,当创建对象的时候,每个对象都有一份属性.一个对象中的属性就是成员变量. 2.本地变量(局部变量) 在方法内声明的变量 ...
- SQL语句分类和语法
DQL:Data QueryLanguage 数据查询语言 作用: 查询表中的字段 命令: select 查询 ⑦select 查询列表①from 表1 别名②连接类型 joi ...
- spring(四):Resource
Resource Spring的Resource接口代表底层外部资源,提供了对底层外部资源的一致性访问接口. public interface Resource extends InputStream ...
- ssh连不上的问题
新安装的ubuntu,想要ssh远程连接,发现连接不上,何解? 答 : 在ubuntu上安装ssh. sudo apt-get install openssh-server
- 数位DP 不要62
数位DP的问法是从某个数到某个数的区间里,求出满足题目要求的个数: 如本题所说的不要62和4,就是求出这个区间内,满足这一条件的数: 比如问 6 199的这个区间内满足条件的数,那么就求出1到199满 ...
- NOIP2012 疫情控制 题解(LuoguP1084)
NOIP2012 疫情控制 题解(LuoguP1084) 不难发现,如果一个点向上移动一定能控制更多的点,所以可以二分时间,判断是否可行. 但根节点不能不能控制,存在以当前时间可以走到根节点的点,可使 ...
- 安装pecl
$ wget http://pear.php.net/go-pear.phar $ php go-pear.phar //php版本 < 7 $ yum install php-pear // ...
- Linux上后台运行node和springboot服务
环境:Ubuntu18.04 阿里云云服务器 尝试全局安装forever和pm2均失败,最后以linux自带的nohub启动,以前同样用nohub启动springboot 命令: nohup npm ...
- Python爬虫连载7-cookie的保存与读取、SSL讲解
一.cookie的保存与读取 1.cookie的保存-FileCookie.Jar from urllib import request,parse from http import cookieja ...
- main函数的参数详解
1.定义 C语言规定main函数的参数只能有两个,习惯上这两个参数写为argc和argv.因此,main函数的函数头可写为: main (argc,argv)C语言还规定argc(第一个形参)必须是整 ...