今天我们了解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 @Value annotation, accessed through Spring’s Environment abstraction, or be bound to structured objects through @ConfigurationProperties.

Spring Boot uses a very particular PropertySource order that is designed to allow sensible overriding of values. Properties are considered in the following order:

  1. Devtools global settings properties in the $HOME/.config/spring-boot folder when devtools is active.
  2. @TestPropertySource annotations on your tests.
  3. properties attribute on your tests. Available on @SpringBootTest and the test annotations for testing a particular slice of your application.
  4. Command line arguments.
  5. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).
  6. ServletConfig init parameters.
  7. ServletContext init parameters.
  8. JNDI attributes from java:comp/env.
  9. Java System properties (System.getProperties()).
  10. OS environment variables.
  11. A RandomValuePropertySource that has properties only in random.*.
  12. Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants).
  13. Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants).
  14. Application properties outside of your packaged jar (application.properties and YAML variants).
  15. Application properties packaged inside your jar (application.properties and YAML variants).
  16. @PropertySource annotations on your @Configuration classes.
  17. 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 需要两步完成使用

  1. 必须要定义一个类来与属性做绑定。

示例说明:

/*
* @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;
}
}
  1. 使用@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特性的更多相关文章

  1. SpringBoot Profiles 多环境配置及切换

    目录 前言 默认环境配置 多环境配置 多环境切换 小结 前言 大部分情况下,我们开发的产品应用都会根据不同的目的,支持运行在不同的环境(Profile)下,比如: 开发环境(dev) 测试环境(tes ...

  2. SpringBoot的特性

    SpringBoot的理念“习惯优于配置” 习惯优于配置(项目中存在大量的配置,此外还内置了一个习惯性的配置,无须手动进行配置) 使用SpringBoot可以方便地创建独立运行.准生产级别的基于Spr ...

  3. SpringBoot核心特性之组件自动装配

    写在前面 spring boot能够根据依赖的jar包自动配置spring boot的应用,例如: 如果类路径中存在DispatcherServlet类,就会自动配置springMvc相关的Bean. ...

  4. 一文掌握 Spring Boot Profiles

    Spring Boot Profiles 简介 Profile 的概念其实很早在 Spring Framework 就有了,在 Spring Framework 3.1 版本引入了注解 @Profil ...

  5. SpringBoot 正式环境必不可少的外部化配置

    前言 <[源码解析]凭什么?spring boot 一个 jar 就能开发 web 项目> 中有读者反应: 部署后运维很不方便,比较修改一个 IP 配置,需要重新打包. 这一点我是深有体会 ...

  6. 助力SpringBoot自动配置的条件注解ConditionalOnXXX分析--SpringBoot源码(三)

    注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 前言 本篇接 如何分析SpringBoot源码模块及结构?--SpringBoot源码(二) 上一篇分析了SpringBoo ...

  7. Spring-boot:5分钟整合Dubbo构建分布式服务

    概述: Dubbo是Alibaba开源的分布式服务框架,它最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者最大限度地松耦合).从服务模型的角度来看,Dubbo采用的是一种非常 ...

  8. SpringBoot简介

    Spring Boot,简单讲就是牺牲项目的自由度来减少配置的复杂度(“契约式编程”思想,SpringBoot自动配置方案的指导思想).约定一套规则,把这些框架都自动配置集成好,从而达到“开箱即用”. ...

  9. Java工程师之SpringBoot系列教程前言&目录

    前言 与时俱进是每一个程序员都应该有的意识,当一个Java程序员在当代步遍布的时候,你就行该想到我能多学点什么.可观的是后端的框架是稳定的,它们能够维持更久的时间在应用中,而不用担心技术的更新换代.但 ...

随机推荐

  1. js加密(二)文书获取

    时间原因直接上代码,有空再解释. js代码: //var tm=new Array(1) //tm[0]=e; ////tm[1]="%u5e72%u82e5%u4f5c%u5de5%u88 ...

  2. JVM的前世今生

    前世 jvm的数据区 分别是方法区(Method Area),Java栈(Java stack),本地方法栈(Native Method Stack),堆(Heap),程序计数器(Program Co ...

  3. layui之laydate

    .点击年份马上关闭窗口并且赋值 html代码: <div class="layui-form-item"> <label class="layui-fo ...

  4. python 日志模块 日志格式

    形如: formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s","%Y%b%d-%H: ...

  5. 2019牛客多校第四场A meeting 思维

    meeting 题意 一个树上有若干点上有人,找出一个集合点,使得所有人都到达这个点的时间最短(无碰撞) 思路 就是找树的直径,找直径的时候记得要找有人的点 #include<bits/stdc ...

  6. 在使用VS过程中关于Javascript没有智能提示的解决方法

    问题:编写基本Script代码没有问题,但是在编写DOM代码时候没有智能提示.也就是在编写一般javascript代码时候没有问题,但是要写DOM代码的时候发现没有智能提示,如document等都需要 ...

  7. linux 网卡限速

    #安装git yum -y install git #下载wondershaper git clone  https://github.com/magnific0/wondershaper.git 第 ...

  8. codeforces 1204C Anna, Svyatoslav and Maps(floyd+dp)

    题目链接:http://codeforces.com/problemset/problem/1204/C 给定一组序列,P1,P2,P3...Pm,这是一组合法路径的序列,即任意的Pi和Pi+1之间有 ...

  9. Redis02——安装Redis

    1.下载获得redis-3.2.5.tar.gz后将它放入我们的Linux目录/opt 2.解压命令:tar -zxvf redis-3.2.5.tar.gz 3.解压完成后进入目录:cd redis ...

  10. SqlDataAdapter对象

    https://blog.csdn.net/qq_39657909/article/details/80615465 一.特点介绍 1.表示用于填充 DataSet 和更新 SQL Server 数据 ...