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" }, ... ]
(2)自定义事件ApplicationEnvironmentPreparedEvent问题:
这里会遇到一个问题,请问为什么这里的7070端口号没有使用呢?
文中
javacode是我们代码中指定的名称。propertySources的取值逻辑是顺序读取,一但有值就会返回。而返回后又对propertySources做了addFirst操作,所以会造成相互覆盖。源码地址: org.springframework.core.env.PropertySourcesPropertyResolver#getProperty(java.lang.String, java.lang.Class, boolean)
要想使用改后的属性,我们可以仿照源码使用下面这种自定义事件ApplicationListener 的方式。
/* * @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 发布!
再次感谢!!! 您已看完全文,欢迎关注微信公众号
猿码,你的支持是我持续更新文章的动力!
- Tomcat 7 的七大新特性
- 谈PHP闭包特性在实际应用中的问题(1)
- Effective C# 利用特性简化反射
- PROTEL 元器件引脚的电气特性
- 关于Mysql 的 ICP、MRR、BKA等特性
- .NET Core CSharp 中级篇2-8 特性标签
- C# 特性(Attribute)之Serializable特性
- NSCoding 协议 父类只需要实现一次,所有子类 都可以 继承 的 runtime特性
- WKWebView新特性及JS交互
- asp.net 4.0 新特性(二)
- 黑马程序员_05Java_JDK1.5新特性总结
- VS2008特性
- JDK 1.5的新特性
- python函数高级特性之生成器
- Java 13 明天发布,最新最全新特性解读
- 三极管的工作特性规律-截止区、放大区、饱和区
- JAVA8 十大新特性详解
- Java 8 新特性:Lambda 表达式的作用域(Lambda 表达式补充版)
- Java 7的新特性
- 项目的一些特性