这节介绍environment,默认环境变量的加载以及初始化。

 之前在介绍spring启动过程讲到,第一步进行环境准备时就会初始化一个StandardEnvironment。下图为Environment类图的接口,可以分为4块内容:

  1. ConversionService(蓝):类型转换服务

  2. PropertySource(绿):键值对数据源

  3. PropertyResolver(红):键值对服务,包括类型转换

  4. 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参数的环境。在通过键名获取键值时,会先根据原键名进行查找,查找不到则通过对键进行转换再尝试查找,具体查找过程为:

  1. 通过name查找

  2. 将name中的 . 转换为 _ 查找

  3. 将name中的 – 转换为 _ 查找

  4. 将name中的 . 和 _ 转换为 – 查找

  5. 将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提供实现的,下面看下占位符的解析。

 占位符的解析过程如上流程,主要过程为:

  1. 根据${前缀得到startIndex

  2. 查找跟${前缀配对的}后缀,如${xxx${yy}z},得到第二个}后缀的下标endIndex

  3. 截取${和}中间的内容得到placeholder

  4. 由于placeholder的内容可能也可能包含占位符,因而要递归处理placeholder,既占位符可以嵌套,内层的结果可以当做外层的Key使用

  5. placeholder解析完后,将其作为Key从键值对源中获取对应的值propVal

  6. 如果propVal值为空,则判断是否存在:分割符,如果有分割符,则进行分割,并使用前端内容作为Key再次查找值。若该次查找结果不为空,则使用该次结果为propVal的值,否则使用第二段内容作为默认值

  7. 若第(5)/(6)步中propVal结果不为空,则判断从键值对源中获取的值是否也有占位符,若有占位符,则再次进行解析,若没有,则将结果替换回原字段中,更新startIndex,继续下次解析。

  8. 若第(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的加载的更多相关文章

  1. Spring源码加载BeanDefinition过程

    本文主要讲解Spring加载xml配置文件的方式,跟踪加载BeanDefinition的全过程. 源码分析 源码的入口 ClassPathXmlApplicationContext构造函数 new C ...

  2. 工厂模式模拟Spring的bean加载过程

    一.前言    在日常的开发过程,经常使用或碰到的设计模式有代理.工厂.单例.反射模式等等.下面就对工厂模式模拟spring的bean加载过程进行解析,如果对工厂模式不熟悉的,具体可以先去学习一下工厂 ...

  3. 监听器如何获取Spring配置文件(加载生成Spring容器)

    Spring容器是生成Bean的工厂,我们在做项目的时候,会用到监听器去获取spring的配置文件,然后从中拿出我们需要的bean出来,比如做网站首页,假设商品的后台业务逻辑都做好了,我们需要创建一个 ...

  4. Spring源码加载过程图解(一)

    最近看了一下Spring源码加载的简装版本,为了更好的理解,所以在绘图的基础上,进行了一些总结.(图画是为了理解和便于记忆Spring架构) Spring的核心是IOC(控制反转)和AOP(面向切面编 ...

  5. spring 配置文件被加载两次

    如下web.xml示例: 1.用spring的配置加载contextConfigLocation 2.配置spring-mvc的contextConfigLocation <servlet> ...

  6. 【Spring】Junit加载Spring容器作单元测试(整理)

    [Spring]Junit加载Spring容器作单元测试 阅读目录 >引入相关Jar包 > 配置文件加载方式 > 原始的用法 > 常见的用法 > 引入相关Jar包 一.均 ...

  7. 深入Spring:自定义注解加载和使用

    前言 在工作中经常使用Spring的相关框架,免不了去看一下Spring的实现方法,了解一下Spring内部的处理逻辑.特别是开发Web应用时,我们会频繁的定义@Controller,@Service ...

  8. Spring Cloud配置文件加载优先级简述

    Spring Cloud中配置文件的加载机制与其它的Spring Boot应用存在不一样的地方:如它引入了bootstrap.properties的配置文件,同时也支持从配置中心中加载配置文件等:本文 ...

  9. spring boot启动加载项CommandLineRunner

    spring boot启动加载项CommandLineRunner 在使用SpringBoot构建项目时,我们通常有一些预先数据的加载.那么SpringBoot提供了一个简单的方式来实现–Comman ...

随机推荐

  1. vue-cli3.x创建及运行项目

    Node 版本要求 Vue CLI 需要 Node.js 8.9 或更高版本 (推荐 8.11.0+).如果你已经全局安装了旧版本的 vue-cli (1.x 或 2.x),你需要先通过 npm un ...

  2. 洛谷 P2341 【受欢迎的牛】

    题库:洛谷 题号:2341 题目:受欢迎的牛 link:https://www.luogu.org/problemnew/show/P2341 思路:因为奶牛的爱慕关系具有传递性,所以每个环(强连通分 ...

  3. codeforces-214(Div. 2)-C. Dima and Salad+DP恰好背包花费

    codeforces-214(Div. 2)-C. Dima and Salad 题意:有不同的沙拉,对应不同的颜值和卡路里,现在要求取出总颜值尽可能高的沙拉,同时要满足 解法:首先要把除法变成乘法, ...

  4. CCPC 网络赛

    array 做法 比赛中的表现..... 已经无法言语形容了. 题意是,查询前缀中大于某个数字的 mex,在线. 一下把问题转化为偏序问题.... 带修主席树?????这下好,直接一箭穿心,武将被移除 ...

  5. 题解 bzoj 2151 种树

    题意 传送门 手写堆大法好啊,题解貌似没有结构体堆的做法,思路有些像配对堆,关于配对堆请自行百度,因为本蒟蒻不会.. 以下是蒟蒻的做法:建立一个大根堆a维护最大价值里面存入它的编号以及价值.听说配对堆 ...

  6. PHP-02.文件上传、php保存/转移上传的文件、常见的网络传输协议、请求报文及属性、响应报文及属性

    关系数组 array("key"=>"value",...) ; get没有数据大小的限制 post上传大小没有限制 不指定上传方式,默认是get 文件上 ...

  7. QRowTable表格控件(五)-重写表头排序、支持第三次单击恢复默认排序

    目录 一.原生表格 二.效果展示 三.实现方式 1.排序列定制 2.排序交互修改 四.相关文章 原文链接:QRowTable表格控件(五)-重写表头排序.支持第三次单击恢复默认排序 一.原生表格 开发 ...

  8. HABSE表结构理解

    也分为行列,行是索引,锁定数据,查找数据只能通过行 列:建表时必须知道列族,真实列(列簇)在插入数据时候可以指定        查找指定列,必须带列族,列族1:name

  9. 一个vue练手的小项目

    编程路上的菜鸟一枚 : 最近接触了vue 然后写了一个练手的项目 使用vue-cli脚手架来搭建了的项目 技术: vue2  + vue-router  + ES6 + axios 框架有 mint- ...

  10. 让API实现版本管理的实践

    API版本管理的重要性不言而喻,对于API的设计者和使用者而言,版本管理都有着非常重要的意义.下面会从WEB API 版本管理的角度提供几种常见办法: 首先,对于API的设计和实现者而言,需要考虑向后 ...