深入了解 Spring 之 Environment
一. 概述
在进行业务代码开发时,不单单只包含业务代码,同时也会包含环境信息;业务代码会根据环境信息从而采取不同的业务处理;在 spring 中,有关环境信息的全部都由 Environment 接口的实现类保存。我们带着相关的疑问去解读;
Environment 接口的实现类的数据结构是怎么样的,其是如何运作的?
环境信息是如何保存到 Environment 中的?
业务代码是如何于 Environment 交互的?
二. 原理
1. Environment 类图
在 spring 中,Environment 的继承关系以及该实现类中所含有的关键属性的结构,类图如下:
类图中的方法,就不过多介绍了;接下来简单介绍一些关键的类。
AbstractEnvironment 是抽象类,里面包含关键的属性信息
activeProfiles 激活的 profile 标志列表,从 MutablePropertySources 中找到以"spring.profiles.active"对应的字符串,多个以","分隔开;
defaultProfiles 默认的 profile 标志列表,从 MutablePropertySources 中找到以"spring.profiles.default"对应的字符串,多个以","分隔开;
propertySources 环境信息都保存在这里
propertyResolver 环境信息解析,主要是类型转换;实现类为 PropertySourcesPropertyResolver
MutablePropertySources 保存所有环境信息的门面,其属性是 PropertySource 集合;意味着有很多不同来源的环境信息;
PropertySource 真正存储环境信息的地方,
name 标明来源方式的名称
source 环境信息的数据存储,之所以采用泛型,是因为其提供更加灵活处理,然而其限制了行为方式。
2. PropertySource
上文说到,其是真正存储环境信息的地方;我们查看其提供的方法,代码如下:
子类会很多,我们挑几个进行简单的介绍:
MapPropertySource T 的类型为 Map 类型,所以其方法很简单,代码如下:
SimpleCommandLinePropertySource T 的类型为 CommandLineArgs,这个是 spring boot 框架启动时,传入的参数;其逻辑算相对于有点复杂,代码如下:
想要看清楚上面的逻辑,就需要了解这个 CommandLineArgs 对象的数据结构,如下:
optionArgs 类型为 Map<String, List<String>>
nonOptionArgs 类型为 List<String>
其逻辑就不言而喻了,相对于简单;至于 CommandLineArgs 是怎么样创建的,以及属性的值是怎么设置上去的,稍后在讲解;
另外的一个重点问题:如果有多个 PropertySource 含有同样的 key 对应的值,具体是以那个 PropertySource 为准呢?说白了就是优先级问题。我们通过代码,可以快速的找到原因。PropertySourcesPropertyResolver 的 getProperty 方法,代码如下:
3. PropertySourcesPropertyResolver
该类是真正根据 key 从 MapPropertySource 找到环境信息对应的值接着进行解析,然后对其进行类型转换,类图如下:
这里稍微留意的是,PropertySourcesPropertyResolver 类中的 propertySources 与 AbstractEnvironment 中的 propertySources 都指向同一个内存地址,意味着我们通过 AbstractEnvironment 获取 propertySources,对其进行操作也会起到对 PropertySourcesPropertyResolver 中的 propertySources 同样的操作;之所以需要两个副本存在,主要是因为 AbstractEnvironment 是一个门面;
现在我们重点介绍 AbstractPropertyResolver 抽象类的关键属性:
conversionService 类型转换类
nonStrictHelper 宽松的属性解析 ,即 ignoreUnresolvableNestedPlaceholders 为 true;
strictHelper 严格的属性解析,即 ignoreUnresolvableNestedPlaceholders 为 false;
ignoreUnresolvableNestedPlaceholders 是否忽略未能解析值,意思是解析占位符拿到的值,从环境变量里面查找;
值为 true 时,如果从 MutablePropertySources 对象找不到对应的值,则原封不动的返回出去
值为 false 时,如果从 MutablePropertySources 对象找不到对应的值,则直接抛异常;
placeholderPrefix 占位符前缀,默认值"${"
placeholderSuffix 占位符后缀,默认值"}"
valueSeparator 值分隔符,默认值":"。主要用于当从环境信息中获去不到这个值,就使用这个分隔符后面的值
requiredProperties key 集合必须在环境信息中必须存在,如果不存在,则直接抛异常;
具体逻辑:解析占位符 ${variable:defaultValue},我们拿到 variable,接着从 MutablePropertySources 对象找到 variable 的值;如果找不到,则使用 defaultValue;
具体代码就不罗列了,感兴趣的,可以直接去看代码;
有关类型转换的,可以从看另外一篇《Spring 类型转换》
三. 构建
我们通过原理的大体的介绍,对其认知有进一步的了解;那么 spring 是如何构建以及初始化 Environment 对象的;
1. 创建
在 AbstractApplicationContext 中的 refresh 方法会判断 Environment 是否为空。如果为空,直接通过空构造函数创建 StandardEnvironment。在空构造函数中,会优先初始化两个 PropertySource,代码如下:
2. 初始化 MutablePropertySources
如在 spring boot 框架中,其会解析传过来的参数进行解析从创建 SimpleCommandLinePropertySource 对象。具体如何解析交由 SimpleCommandLineArgsParser 对象来处理;
处理规则是:
--key=value 归属 optionArgs
key=value 归属 nonOptionArgs
并且插入 MutablePropertySources 对象集合中头部,即优先级最高;别名为:springApplicationCommandLineArgs
另外 spring boot 框架,提供了 EnvironmentPostProcessor 这个接口;spring boot 应用启动过程中,就可以对 Environment 对象进行操作;我们查看其接口的关键的实现类,其余类就不在展示:
RandomValuePropertySourceEnvironmentPostProcessor 创建 RandomValuePropertySource 对象,插入到 MutablePropertySources 尾部;其根据前缀"random."+type(类型)。类型主要有 int,long,uuid,byte 数组。具体可以查看该类
ConfigFileApplicationListener 有关解析 application.*配置文件;插入到 MutablePropertySources 尾部;优先级是 profile->default;
之所以 EnvironmentPostProcessor 会生效,是由于在 spring boot 框架中添加了事件机制;所以 EnvironmentPostProcessor 只在 spring boot 框架中使用,原生的 spring 没有该功能;
原生的提供了 @PropertySource、@PropertySources 注解,可以加载该注解指定的配置文件,解析该配置文件封装成 PropertySource 对象保存到 MutablePropertySources 尾部;该注解生效的前提是向 SpringContext 中添加 ConfigurationClassPostProcessor 处理器,同时在添加该注解的类添加 @Configuration 注解;
四. 运用
在 spring 启动过程已经对 Environment 进行初始化了,那么我们是如何使用该对象的;
1. EnvironmentAware
在类中通过实现该接口,spring 容器会自动帮我们注入 Environment 对象;这样子我们就可以拿到这个对象进行我们想要的操作;
2. StringValueResolver
该接口是对字符串进行解析,匿名内部类,代码如下:
在 BeanFactory.doResolveDependency 方法中会调用 embeddedValueResolvers 进行字符串解析;
在我们常规开发中,对象中属性使用 @Value("${}"),就会触发上面的解析从 Environment 对象中查找对应的 value 进行替换;
同时 PropertySourcesPlaceholderConfigurer 类对 BeanDefinition 中标有 ${}的 factoryMethodName、beanClass 等等进行替换;具体还得看里面的代码,关键的代码 BeanDefinitionVisitor:
五. Profile
之所以单独拿来讲述,因为其会影响对象是否能保存到 spring 容器;
当一个类标有 @Profile 时,其会根据 Environment 变量中的 profile 来决定是否需要实例化该对象并且注入到容器中去;
具体是有 ProfileCondition 对象去判断,代码如下:
版权声明: 本文为 InfoQ 作者【邱学喆】的原创文章。
原文链接:【http://xie.infoq.cn/article/6c09ffaa51ceb2e9a415e5749】。文章转载请联系作者。
评论