Spring Environment的加载
这节介绍environment,默认环境变量的加载以及初始化。
之前在介绍spring启动过程讲到,第一步进行环境准备时就会初始化一个StandardEnvironment。下图为Environment类图的接口,可以分为4块内容:
ConversionService(蓝):类型转换服务
PropertySource(绿):键值对数据源
PropertyResolver(红):键值对服务,包括类型转换
Environment(紫):环境配置数据服务

1.ConversionService
提供了类型转换服务,能将源目标转换为目标类型,同时提供了管理功能,内部维护了各类型转换映射关系。其实从ConversionService和ConverterRegistry接口就能看出该模块的功能,如下:

ConversionService接口为主要的对外功能接口,提供查询的能力。

ConverterRegistry接口为主要的管理接口,提供添加和删除的能力。而ConfigurableConversionService继承自上面二者,则提供了Converter的CRUD功能。结构上也延续了Spring固有的风格,将执行接口作为主要功能对外提供单一的接口,再通过继承的方式,以Configurable开头的子接口,扩展出管理功能,使得责任分离更加立体。
接下来是GenericConversionService类,该类提供了接口全部实现,下图展示了其主要实现:

GenericConversionService结构上可以说是一个小型的管理系统,内部维护了一个Converters对象,用于“底层”管理所有的GenericConverter。同时还维护了一个ConcurrentReferenceHashMap用于缓存常用的GenericConverter。
Converters在存储GenericConverter时还进行了分类,如果GenericConverter有指定能够解析的类别(ConvertiablePair:包括SourceType和TargetType)时,则使用一个LinkedHashMap按Key Value进行存储,在存储时会遍历可解析的类别,将该GenericConverter追加到对应的Value列表末尾,因而可以看到该Map的Value是一个LinkedList。对应没有指定能解析的类别的GenericConverter,则直接放到LinkedHashSet维护的集合中。
Converters在查询时会遍历源类型和目标类型的组合结果,以查找匹配的目标GenericConverter对象。如下:

对于getRegisteredConverter方法,会先使用Key从LinkedHashMap中查找是否有匹配的Converter,再遍历相应的Value,查找到能处理的转换器。若Map中无法查到,则遍历LinkedHashSet,以查到到能处理的转换器。
由上知道,Converters在查找时存在多次遍历列表的过程,在频率过多时效率会比较低下,因而GenericConversionService内部维护了一个ConcurrentReferenceHashMap提供缓存的功能,该Map提供了同ConcurrentHashMap相同的功能,但是能够存储对应的软引用,从而能在内存不足时自动进行内存回收。在查到转换器时,会先试着从缓存中查找,如果获取不到,则会转而从Converters中查找,当从Converters中查找到后便会put到ConcurrentReferenceHashMap缓存中。
DefaultConversionService是一个单例,继承自GenericConversionService,在初始化后自动添加了默认的转换器,包括Scalar相关的、集合相关的等转换器。
2.PropertySource
PropertySource代表了一个包含键值对的数据源。从类定义上看,有一个表示数据源名字的name字段,还有一个表示具体数据源泛型T的source字段。而数据源的设置则是通过构造方法传入的,同时方法提供了通过键名获取键值的抽象方法getProperty。此外还有其他抽象方法,如containsProperty等。
EnumerablePropertySource继承自PropertySource,增加了getPropertyNames方法,要求子类返回内存持有的键名列表。同时实现了containsProperty方法,通过判断所给的键名是否存在上述返回的键名列表中从而判断是否包含该键名。
MapPropertySource继承自EnumerablePropertySource,顾名思义,内部通过Map维护各键值对内容。类似的还有PropertiesPropertySource,内部通过Properties维护各键值对内容。
SystemEnvironmentPropertySource是MapPropertySource的装饰器,继承自MapPropertySource,为其添加了键名转换功能,以应对环境变量、shell参数的环境。在通过键名获取键值时,会先根据原键名进行查找,查找不到则通过对键进行转换再尝试查找,具体查找过程为:
通过name查找
将name中的 . 转换为 _ 查找
将name中的 – 转换为 _ 查找
将name中的 . 和 _ 转换为 – 查找
将name转换为大写,再进行(1) - (4)的过程
PropertySources的实现如下,扩展了PropertySource接口,将单个数据源的能力扩展到了多个。MutablePropertySources作为PropertySources的实现,内部维护了一个List对象,用以存储多个数据源,并将自身的行为封装为List。

3.PropertyResolver
PropertyResolver定义了一系列接口,以提供了对外根据键名获取相应值的功能,同时提供了类型转换和占位符替换的功能,是ConversionService和PropertySource的结合。ConfigurablePropertyResolver接口继承自PropertyResolver接口,老规则,扩展了设置的功能,主要是设置类型转换器和占位符的相关属性。
AbstractPropertyResolver提供了除PropertySource功能外的其余实现。使用DefaultConversionService作为默认的类型转换实现,使用 ${ 和 } 作为占位符的前后缀,使用:作为默认值分割符,同时引入PropertyPlaceholderHelper用于占位符的解析和替换。而getProperty的实现则留到了了子类PropertySourcesPropertyResolver中,其引入了PropertySources用以维护多个键值对数据源。获取指定属性值过程如下:

通过遍历数据源的方式,查到对应的值后,会进行占位符的替换,替换完占位符后会进行类型的转换。类型转换直接用的DefaultConversionService,这个上面已经介绍过了,下面介绍占位符替换。
占位符替换的功能是在PropertyResolver接口中定义的,分为严格和不严格模式,如下:

resolvePlaceholders为不严格模式,如果没法替换占位符,则直接忽略,resolveRequiredPlaceholders为严格模式,如果占位符没法替换则会抛出异常。如上面说的,AbstractPropertyResolver实现时都委托给PropertyPlaceholderHelper的replacePlaceholders方法。

如上,该方法要求传入一个源字符串,同时提供一个PlaceholderResolver数据源,一遍解析出占位符内容后能够从数据源中获取对应的值。为了保持类功能的单一职责,从而增加了一个内部接口PlaceholderResolver。上面提到,在这个模块中的键值对数据源都是由PropertySourcesPropertyResolver维护的,事实上上面方法截图的实现中,getPropertyAsRawString方法也确实是由PropertySourcesPropertyResolver提供实现的,下面看下占位符的解析。

占位符的解析过程如上流程,主要过程为:
根据${前缀得到startIndex
查找跟${前缀配对的}后缀,如${xxx${yy}z},得到第二个}后缀的下标endIndex
截取${和}中间的内容得到placeholder
由于placeholder的内容可能也可能包含占位符,因而要递归处理placeholder,既占位符可以嵌套,内层的结果可以当做外层的Key使用
placeholder解析完后,将其作为Key从键值对源中获取对应的值propVal
如果propVal值为空,则判断是否存在:分割符,如果有分割符,则进行分割,并使用前端内容作为Key再次查找值。若该次查找结果不为空,则使用该次结果为propVal的值,否则使用第二段内容作为默认值
若第(5)/(6)步中propVal结果不为空,则判断从键值对源中获取的值是否也有占位符,若有占位符,则再次进行解析,若没有,则将结果替换回原字段中,更新startIndex,继续下次解析。
若第(5)/(6)步中propVal结果不空,则会根据设置的解析模式来判断下一步行为,如果未不严格模式,则跳过该次内容,更新startIndex,继续下一次解析,若为严格模式,则抛出异常,流程结束。
下面以一个例子进行演示,如下

输出结果为:

若将解析模式设置为严格模式则会抛出异常
4.Environment
Environment继承自PropertyResolver接口,增加了Profiles功能,即我们平时看到的,多环境特性,能够在不同环境下加载不同的配置。ConfigurableEnvironment继承自Environment,老规矩,又是添加了修改的扩展接口,同时增加了获取系统参数的接口。另外,该接口也继承自ConfigurablePropertyResolver,有了键值对数据源管理、获取和处理的能力,集合Environment接口的功能,能够达到在不同环境下通过加载不同配置源实现环境隔离的效果。
AbstractEnvironment是ConfigurablePropertyResolver的实现,提供了默认的环境源default,同时内部组合使用PropertySourcesPropertyResolver作为PropertyResolver的实现。
它还维护了一个MutablePropertySources对象,用于存储多个数据源,在Context的父子上下文中,通过merge方法,能够将父上文中的环境变量内容添加进来(在AbstractApplicationContext设置父Context时,会将父Environment进行合并)。同时还有一个方法customizePropertySources,会在构造方法中进行调用,开放给子类添加默认的键值对源,如下:

最后是StandardEnvironment类,继承自AbstractEnvironment,重写了customizePropertySources方法,在该方法中添加了系统相关的属性和应用环境变量相关的属性的键值对源。如下

而这两个数据源来自于前面提到的PropertySource实现。其中,系统相关属性SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME的数据源来源于System.getProperties(),而应用环境变量相关属性SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME则来源于System.getenv()。

个人公众号:啊驼
Spring Environment的加载的更多相关文章
- Spring源码加载BeanDefinition过程
本文主要讲解Spring加载xml配置文件的方式,跟踪加载BeanDefinition的全过程. 源码分析 源码的入口 ClassPathXmlApplicationContext构造函数 new C ...
- 工厂模式模拟Spring的bean加载过程
一.前言 在日常的开发过程,经常使用或碰到的设计模式有代理.工厂.单例.反射模式等等.下面就对工厂模式模拟spring的bean加载过程进行解析,如果对工厂模式不熟悉的,具体可以先去学习一下工厂 ...
- 监听器如何获取Spring配置文件(加载生成Spring容器)
Spring容器是生成Bean的工厂,我们在做项目的时候,会用到监听器去获取spring的配置文件,然后从中拿出我们需要的bean出来,比如做网站首页,假设商品的后台业务逻辑都做好了,我们需要创建一个 ...
- Spring源码加载过程图解(一)
最近看了一下Spring源码加载的简装版本,为了更好的理解,所以在绘图的基础上,进行了一些总结.(图画是为了理解和便于记忆Spring架构) Spring的核心是IOC(控制反转)和AOP(面向切面编 ...
- spring 配置文件被加载两次
如下web.xml示例: 1.用spring的配置加载contextConfigLocation 2.配置spring-mvc的contextConfigLocation <servlet> ...
- 【Spring】Junit加载Spring容器作单元测试(整理)
[Spring]Junit加载Spring容器作单元测试 阅读目录 >引入相关Jar包 > 配置文件加载方式 > 原始的用法 > 常见的用法 > 引入相关Jar包 一.均 ...
- 深入Spring:自定义注解加载和使用
前言 在工作中经常使用Spring的相关框架,免不了去看一下Spring的实现方法,了解一下Spring内部的处理逻辑.特别是开发Web应用时,我们会频繁的定义@Controller,@Service ...
- Spring Cloud配置文件加载优先级简述
Spring Cloud中配置文件的加载机制与其它的Spring Boot应用存在不一样的地方:如它引入了bootstrap.properties的配置文件,同时也支持从配置中心中加载配置文件等:本文 ...
- spring boot启动加载项CommandLineRunner
spring boot启动加载项CommandLineRunner 在使用SpringBoot构建项目时,我们通常有一些预先数据的加载.那么SpringBoot提供了一个简单的方式来实现–Comman ...
随机推荐
- MySQL8版本密码重置(老版本skip-grant-tables不起作用,MySQL服务开启之后立马关闭)
原文:https://blog.csdn.net/gupao123456/article/details/80766154 MySQL密码重置思路MySQL的密码是存放在user表里面的,修改密码其 ...
- Python数据类型详解——列表
Python数据类型详解--列表 在"Python之基本数据类型概览"一节中,大概介绍了列表的基本用法,本节我们详细学一下列表. 如何定义列表:在[]内以英文里输入法的逗号,,按照 ...
- 新手学习FFmpeg - 调用API完成录屏并进行H.264编码
Screen Record H.264 目前在网络传输视频/音频流都一般会采用H.264进行编码,所以尝试调用FFMPEG API完成Mac录屏功能,同时编码为H.264格式. 在上一篇文章中,通过调 ...
- 选择排序、快速排序、归并排序、堆排序、快速排序实现及Sort()函数使用
1.问题来源 在刷题是遇到字符串相关问题中使用 strcmp()函数. 在函数比较过程中有使用 排序函数 Sort(beg,end,comp),其中comp这一项理解不是很彻底. #include & ...
- cogs1709. [SPOJ 705] 不同的子串(后缀数组
http://cogs.pro:8080/cogs/problem/problem.php?pid=vyziQkWaP 题意:给定一个字符串,计算其不同的子串个数. 思路:ans=总共子串个数-相同的 ...
- 《2019面向对象程序设计(java)课程学习进度条》
学习资源 1.教材P28-P76 2.第3章教学课件3.1-3.8 3.corejava.zip中第3章示例程序3-1—3-5 4.Eclipse简明教程.pdf 5.MOOC & 视频:浙江 ...
- 一小时入门 Python
因为需求, 需要用到py, 所以来学学py, 因为有java基础 一小时入门py语法是不成问题的, 但是仅仅入门基础语法而已, 不涉及算法,不涉及大数据,机器学习,人工智能, 但是py这么火爆,就在于 ...
- 1张影射过往的图片,如何勾起往事的回忆,.CORE其实可以是这样的吗?
看到某人写了一个流程分析貌似可以披云见日,形似之余好像回忆可以相得益彰 然后我刚刚不小心发布了,当然要准备100字的说明,这个字应该怎么打好呢,不知不觉打了好多字,我好难啊 首先这是正常情况看不到的图 ...
- PHP的调试环境程序集成包----phpStudy
PHP (超文本预处理器) PHP即“超文本预处理器”,是一种通用开源脚本语言.PHP是在服务器端执行的脚本语言,与C语言类似,是常用的网站编程语言.PHP独特的语法混合了C.Java.Perl以及 ...
- Spring事务失效的2种情况
使用默认的事务处理方式 因为在java的设计中,它认为不继承RuntimeException的异常是”checkException”或普通异常,如IOException,这些异常在java语法中是要求 ...