交流
商城
MCN
登入
注册
首页
提问
分享
讨论
建议
公告
动态
发表新帖
发表新帖
spring核心第1 章:Environment
分享
未结
0
882
李延
LV6
2021-03-23
悬赏:20积分
# environment功能 ## 功能说明 在environment存储者spring中所有的变量信息,包括系统变量、jvm变量、以及当前程序定义的变量。同时用户可以通过变量名字获取对应的变量值,同时支持:变量值类型转换、变量占位符的解析等功能。 ## 使用实例 ```java final ConfigurableApplicationContext run = SpringApplication.run(Main.class, args); //在applicationContext中有environment我们可以通过它获取 final ConfigurableEnvironment environment = run.getEnvironment(); //获取普通的配置变量 final String test = environment.getProperty("test"); //获取同时,并转换为int类型 final Integer number = environment.getProperty("number", Integer.class); //获取包含占位符的变量 final String placeholderTest = environment.getProperty("placeholderTest${test}"); //获取变量,当不存在时,使用默认值 final String property = environment.getProperty("${default:test}"); ``` # environment源码解析 在StandardEnvironment类中继承关系入下图:  其中主要的接口和方法有 - PropertyResolver - conversion PropertyResolver 主要实现的是变量的获取以及对于占位符的解析 conversion 主要实现的是对于变量值类型的转换 下面详细结束这些接口与类 ## 父类接口说明 ### PropertyResolver 这个是顶级接口,主要定义了getProperty等获取变量相关的方法,同时包含 resolvePlaceholders 解析占位符方法。 ### Environment 在 PropertyResolver 基础上添加了系统运行环境profile相关的接口定义,但当单独解析environmen时无法看出来其作用,故暂不解析。 ### ConfigurablePropertyResolver 在 PropertyResolver 基础上添加可配置项,属性转换器 convension、占位符的前缀、后缀等配置。 ### ConfigurableEnvironment 在 Environment 与 ConfigurablePropertyResolver 基础上添加对环境的设置。暂不解析。 ## 父类class说明 ### AbstractEnvironment 在当前类中,实现了所有的基本方法。除了 环境相关内容。其他都通过 PropertySourcesPropertyResolver来实现。具体解析见下文 ### StandardEnvironment 在父类的基础上添加系统变量、jvm变量 systemEnvironment sytemProperties。 ### MutablePropertySources MutablePropertySources 是PropertySources的子类,在创建PropertySourcesPropertyResolver 对象时需要一个PropertySources对象作为参数。 我们可以看到 MutablePropertySources 就是一个存放批量PropertySource的集合,并且get、add、remove 等相应的方法。 对于 PropertySource 则是存放一个来源的配置。如jvm变量会存放在一个PropertySource 中,而系统变量会全部存在在另一个PropertySource 中,我们平时一个配置文件就是一个PropertySources对象。 至此我们可以看到 在Evironment中。我们所有的配置变量其实都保存在一个y又一个的PropertySource。而所有的 PropertySource又通过list被存放在MutablePropertySources 对象中。 ### PropertySourcesPropertyResolver 我们可以看到在其构造函数中需要一个PropertySources对象,而在AbstractEnvironment中是new出MutablePropertySources传进来的。根据之前的接口分析。其主要方法有getProperty和resolvePlaceholders两个方法接下来详细解析: #### getProperty ```java //所有的getProperty方法最终都会调用这个方法 //key 变量名称,targetValueType 变量类型,resolveNestedPlaceholders 是否解析占位符 @Nullable protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) { if (this.propertySources != null) { //循环遍历MutablePropertySources中的每个propertySource对象,逐一通过get方法获取变量值。 //这也说明MutablePropertySources中排在前面的变量,有着更高的优先度。 for (PropertySource<?> propertySource : this.propertySources) { if (logger.isTraceEnabled()) { logger.trace("Searching for key '" + key + "' in PropertySource '" + propertySource.getName() + "'"); } Object value = propertySource.getProperty(key); if (value != null) { //当resolveNestedPlaceholders 为true时,解析占位符。 if (resolveNestedPlaceholders && value instanceof String) { value = resolveNestedPlaceholders((String) value); } logKeyFound(key, propertySource, value); //如果有必要,转换类型。 return convertValueIfNecessary(value, targetValueType); } } } if (logger.isTraceEnabled()) { logger.trace("Could not find key '" + key + "' in any property source"); } return null; } ``` 在上面的代码中我们可以看到,其实属性的获取,就是去遍历propertySources中的每个propertySource尝试获取值。而对于占位符和类型转换,分别交给了resolveNestedPlaceholders和convertValueIfNecessary 方法 ```java @Nullable private PropertyPlaceholderHelper nonStrictHelper; @Nullable private PropertyPlaceholderHelper strictHelper; protected String resolveNestedPlaceholders(String value) { if (value.isEmpty()) { return value; } return (this.ignoreUnresolvableNestedPlaceholders ? resolvePlaceholders(value) : resolveRequiredPlaceholders(value)); } @Override public String resolvePlaceholders(String text) { if (this.nonStrictHelper == null) { this.nonStrictHelper = createPlaceholderHelper(true); } return doResolvePlaceholders(text, this.nonStrictHelper); } @Override public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException { if (this.strictHelper == null) { this.strictHelper = createPlaceholderHelper(false); } return doResolvePlaceholders(text, this.strictHelper); } private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) { return helper.replacePlaceholders(text, this::getPropertyAsRawString); } ``` 对于占位符的解析我们可以看到最终是委托给了PropertyPlaceholderHelper对象。但在PropertyPlaceholderHelper 中没有获取属性的功能。所有需要通过参数传入getPropertyAsRawString方法获取属性。具体方法见下一节。 ```java @Nullable protected <T> T convertValueIfNecessary(Object value, @Nullable Class<T> targetType) { if (targetType == null) { return (T) value; } ConversionService conversionServiceToUse = this.conversionService; if (conversionServiceToUse == null) { // Avoid initialization of shared DefaultConversionService if // no standard type conversion is needed in the first place... if (ClassUtils.isAssignableValue(targetType, value)) { return (T) value; } conversionServiceToUse = DefaultConversionService.getSharedInstance(); } return conversionServiceToUse.convert(value, targetType); } ``` 对于属性类型的转换是交给了DefaultConversionService 来做。我们将单独解析这个类。 #### resolvePlaceholders ```java @Override public String resolvePlaceholders(String text) { if (this.nonStrictHelper == null) { this.nonStrictHelper = createPlaceholderHelper(true); } return doResolvePlaceholders(text, this.nonStrictHelper); } ``` ### PropertyPlaceholderHelper 在上文中。我们看到对于占位符的解析时通过这个类来进行的,我们将解析他的主要方法parseStringValue ```java //可能包含占位符的字符串, //placeholderResolver通过key获取属性值,该方法不提供占位符解析 //visitedPlaceholders 递归记录以及便利过的属性 protected String parseStringValue( String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) { //判断是否包含占位符 int startIndex = value.indexOf(this.placeholderPrefix); if (startIndex == -1) { return value; } StringBuilder result = new StringBuilder(value); while (startIndex != -1) { //找到对应的结束标记,这个方法考虑的嵌套情况 int endIndex = findPlaceholderEndIndex(result, startIndex); if (endIndex != -1) { //攫取当前字符串 String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex); String originalPlaceholder = placeholder; if (visitedPlaceholders == null) { visitedPlaceholders = new HashSet<>(4); } //判断visitedPlaceholders中是否已经包含当前值,如果有说明以及循环依赖。 if (!visitedPlaceholders.add(originalPlaceholder)) { throw new IllegalArgumentException( "Circular placeholder reference '" + originalPlaceholder + "' in property definitions"); } // 递归调用,确保先解析当前字符串中的占位符 placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); // 已解析后的key获取值 String propVal = placeholderResolver.resolvePlaceholder(placeholder); //当为空时,以valueSeparator判断是否有默认值 if (propVal == null && this.valueSeparator != null) { int separatorIndex = placeholder.indexOf(this.valueSeparator); if (separatorIndex != -1) { String actualPlaceholder = placeholder.substring(0, separatorIndex); String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length()); propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder); if (propVal == null) { propVal = defaultValue; } } } //再次尝试获取值,因为前文中默认值也可能包含占位符 if (propVal != null) { // Recursive invocation, parsing placeholders contained in the // previously resolved placeholder value. propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders); result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal); if (logger.isTraceEnabled()) { logger.trace("Resolved placeholder '" + placeholder + "'"); } startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length()); } else if (this.ignoreUnresolvablePlaceholders) { // Proceed with unprocessed value. startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length()); } else { throw new IllegalArgumentException("Could not resolve placeholder '" + placeholder + "'" + " in value \"" + value + "\""); } visitedPlaceholders.remove(originalPlaceholder); } else { startIndex = -1; } } return result.toString(); } ```
回帖
消灭零回复
提交回复
热议榜
java 相关知识分享
8
好的程序员与不好的程序员
6
写给工程师的十条精进原则
5
spring boot以jar包运行配置的logback日志文件没生成
5
一步一步分析SpringBoot启动源码(一)
5
MockMvc测试
5
【吐槽向】是不是有个吐槽的板块比较好玩
4
logstash jdbc同步mysql多表数据到elasticsearch
3
IntelliJ IDEA 优质License Server
3
.gitignore忽略规则
3
SpringBoot启动源码分析
3
一步一步分析SpringBoot启动源码(三)
3
2
一步一步分析SpringBoot启动源码(二)
2
积分不够将无法发表新帖
2
官方产品
Meta-Boot - 基于MCN
MCN - 快速构建SpringBoot应用
微信扫码关注公众号