【spring源码分析】IOC容器初始化——查漏补缺(五)
前言:我们知道在Spring中经常使用配置文件的形式对进行属性的赋值,那配置文件的值是怎么赋值到属性上的呢,本文将对其进行分析。
首先了解一个类:PropertySourcesPlaceholderConfigurer,该类对程序中使用占位符的方式对属性进行赋值的形式进行解析,如在xml配置文件中进行了key-value的配置,在程序中使用该配置的值的形式。

分析:
从PropertySourcesPlaceholderConfigurer的继承结构图上可知,PropertySourcesPlaceholderConfigurer间接实现了Aware和BeanFactoryPostProcessor两大接口,这里只关注BeanFactoryPostProcessor(Aware接口前面已经分析了),BeanFactoryPostProcessor接口中就只有一个postProcessBeanFactory方法,其实现如下:
// PropertySourcesPlaceholderConfigurer
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 如果propertySources为null,则初始化该属性
if (this.propertySources == null) {
this.propertySources = new MutablePropertySources();
if (this.environment != null) {
this.propertySources.addLast(
new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
@Override
@Nullable
public String getProperty(String key) {
return this.source.getProperty(key);
}
}
);
}
try {
// 构建localPropertySource对象,Properties通过mergeProperties方法获取。
PropertySource<?> localPropertySource =
new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
if (this.localOverride) {
this.propertySources.addFirst(localPropertySource);
}
else {
this.propertySources.addLast(localPropertySource);
}
}
catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
// 进行占位符替换
processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
this.appliedPropertySources = this.propertySources;
}
分析:
- 首先需要构建MutablePropertySources对象,用于存储Properties,注意Properties是通过mergeProperties获取。
- 占位符的替换过程在processProperties函数中实现。
PropertiesLoaderSupport#mergeProperties
protected Properties mergeProperties() throws IOException {
// 创建Properties对象
Properties result = new Properties();
// 是否允许覆盖配置 之前
if (this.localOverride) {
// Load properties from file upfront, to let local properties override.
loadProperties(result);
}
// 进行配置合并
if (this.localProperties != null) {
for (Properties localProp : this.localProperties) {
CollectionUtils.mergePropertiesIntoMap(localProp, result);
}
}
// 再次进行覆盖 之后
if (!this.localOverride) {
// Load properties from file afterwards, to let those properties override.
loadProperties(result);
}
return result;
}
分析:
这里其实逻辑简单,主要通过loadProperties进行配置文件的导入。
PropertiesLoaderSupport#loadProperties
protected void loadProperties(Properties props) throws IOException {
// 遍历文件路径
if (this.locations != null) {
for (Resource location : this.locations) {
if (logger.isTraceEnabled()) {
logger.trace("Loading properties file from " + location);
}
try {
PropertiesLoaderUtils.fillProperties(
props, new EncodedResource(location, this.fileEncoding), this.propertiesPersister);
} catch (FileNotFoundException | UnknownHostException ex) {
if (this.ignoreResourceNotFound) {
if (logger.isDebugEnabled()) {
logger.debug("Properties resource not found: " + ex.getMessage());
}
} else {
throw ex;
}
}
}
}
}
分析:
这里代码逻辑简单,变量locations对象,然后通过PropertiesLoaderUtils#fillProperties方法进行属性填充,这里不对该方法进行分析,有兴趣的可以自己详细看下。
PropertiesLoaderSupport#processProperties
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
final ConfigurablePropertyResolver propertyResolver) throws BeansException {
// 设置"${"、"}"、":"
propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
propertyResolver.setValueSeparator(this.valueSeparator); // 根据是否需要忽略不能解析的符号进行分支处理
StringValueResolver valueResolver = strVal -> {
String resolved = (this.ignoreUnresolvablePlaceholders ?
propertyResolver.resolvePlaceholders(strVal) :
propertyResolver.resolveRequiredPlaceholders(strVal));
if (this.trimValues) {
resolved = resolved.trim();
}
return (resolved.equals(this.nullValue) ? null : resolved);
}; // 进行具体解析
doProcessProperties(beanFactoryToProcess, valueResolver);
}
分析:
从函数的具体逻辑可以看出Spring中对占位符的解析只包含"${ }"和":"。
- 创建StringValueResolver对象,根据不同的策略(是否要忽略不能解析的符号),这里会在doProcessProperties中进行回调。其实resolvePlaceholders和resolveRequiredPlaceholders函数内部都调用的同一个函数,只是其初始化方式不同(false和true的区别),具体代码AbstractPropertyResolver中。
- 最终的具体解析过程落在doProcessProperties函数中。
AbstractPropertyResolver#resolveRequiredPlaceholders
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
// 如果strictHelper为null,则通过createPlaceholderHelper进行创建
// 注意这里createPlaceholderHelper入参为false,这就是和resolvePlaceholders不一样的地方
if (this.strictHelper == null) {
this.strictHelper = createPlaceholderHelper(false);
}
// 通过PropertyPlaceholderHelper进行解析
return doResolvePlaceholders(text, this.strictHelper);
}
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
this.valueSeparator, ignoreUnresolvablePlaceholders);
}
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}
// PropertyPlaceholderHelper
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "'value' must not be null");
return parseStringValue(value, placeholderResolver, new HashSet<>());
}
分析:
以上代码逻辑还是比较简单,最终的解析落入PropertyPlaceholderHelper#replacePlaceHolders函数中,该函数在【spring源码分析】IOC容器初始化(一)中已分析。
PlaceholderConfigurerSupport#doProcessProperties
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
StringValueResolver valueResolver) { // 创建BeanDefinitionVisitor对象
BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver); // 对BeanDefinition进行遍历
String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
for (String curName : beanNames) {
// Check that we're not parsing our own bean definition,
// to avoid failing on unresolvable placeholders in properties file locations.
// 校验
// 当前PlaceholderConfigurerSupport不在解析范围内 同一个Spring容器
if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
try {
// 访问bean,其实是对属性进行解析
visitor.visitBeanDefinition(bd);
}
catch (Exception ex) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
}
}
} // New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
// 别名占位符
beanFactoryToProcess.resolveAliases(valueResolver); // New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
// 解析嵌入值的占位符,例如注释属性
beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}
分析:
- 首先创建BeanDefinitionVisitor对象,通过字符串解析器,用于后期对配置文件进行解析。
- 对BeanDefinition进行遍历,通过visitBeanDefinition方法进行配置文件解析。
- 解析别名占位符。
- 解析嵌入值的占位符,例如注释属性。
其实整个方法的核心在于visitBeanDefinition方法。
BeanDefinitionVisitor#visitBeanDefinition
public void visitBeanDefinition(BeanDefinition beanDefinition) {
visitParentName(beanDefinition);
visitBeanClassName(beanDefinition);
visitFactoryBeanName(beanDefinition);
visitFactoryMethodName(beanDefinition);
visitScope(beanDefinition);
if (beanDefinition.hasPropertyValues()) {
visitPropertyValues(beanDefinition.getPropertyValues());
}
if (beanDefinition.hasConstructorArgumentValues()) {
ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
visitIndexedArgumentValues(cas.getIndexedArgumentValues());
visitGenericArgumentValues(cas.getGenericArgumentValues());
}
}
分析:
该方法基本访问了BeanDefinition中所有值得访问的东西了,包括parent 、class 、factory-bean 、factory-method 、scope 、property 、constructor-arg。
这里关注visitPropertyValues方法。
BeanDefinitionVisitor#visitPropertyValues
protected void visitPropertyValues(MutablePropertyValues pvs) {
// 遍历PropertyValue数组
PropertyValue[] pvArray = pvs.getPropertyValues();
for (PropertyValue pv : pvArray) {
// 解析真值
Object newVal = resolveValue(pv.getValue());
if (!ObjectUtils.nullSafeEquals(newVal, pv.getValue())) {
// 设置到PropertyValue中
pvs.add(pv.getName(), newVal);
}
}
}
分析:
该函数主要就是对属性数组进行遍历,通过resolveValue方法对属性进行解析获取最新值,如果新值与就只不相等,则用新值替换旧值。
BeanDefinitionVisitor#resolveValue
protected Object resolveValue(@Nullable Object value) {
if (value instanceof BeanDefinition) {
visitBeanDefinition((BeanDefinition) value);
}
else if (value instanceof BeanDefinitionHolder) {
visitBeanDefinition(((BeanDefinitionHolder) value).getBeanDefinition());
}
else if (value instanceof RuntimeBeanReference) {
RuntimeBeanReference ref = (RuntimeBeanReference) value;
String newBeanName = resolveStringValue(ref.getBeanName());
if (newBeanName == null) {
return null;
}
if (!newBeanName.equals(ref.getBeanName())) {
return new RuntimeBeanReference(newBeanName);
}
}
else if (value instanceof RuntimeBeanNameReference) {
RuntimeBeanNameReference ref = (RuntimeBeanNameReference) value;
String newBeanName = resolveStringValue(ref.getBeanName());
if (newBeanName == null) {
return null;
}
if (!newBeanName.equals(ref.getBeanName())) {
return new RuntimeBeanNameReference(newBeanName);
}
}
else if (value instanceof Object[]) {
visitArray((Object[]) value);
}
else if (value instanceof List) {
visitList((List) value);
}
else if (value instanceof Set) {
visitSet((Set) value);
}
else if (value instanceof Map) {
visitMap((Map) value);
}
else if (value instanceof TypedStringValue) {
TypedStringValue typedStringValue = (TypedStringValue) value;
String stringValue = typedStringValue.getValue();
if (stringValue != null) {
String visitedString = resolveStringValue(stringValue);
typedStringValue.setValue(visitedString);
}
}
// 由于Properties中的是String,所以重点在此
else if (value instanceof String) {
return resolveStringValue((String) value);
}
return value;
}
分析:
该函数对各种类型的属性进行解析,由于配置为String类型的,因此这里关注resolveStringValue函数。
BeanDefinitionVisitor#resolveStringValue
protected String resolveStringValue(String strVal) {
if (this.valueResolver == null) {
throw new IllegalStateException("No StringValueResolver specified - pass a resolver " +
"object into the constructor or override the 'resolveStringValue' method");
}
// 解析真值
String resolvedValue = this.valueResolver.resolveStringValue(strVal);
// Return original String if not modified.
return (strVal.equals(resolvedValue) ? strVal : resolvedValue);
}
分析:
这里具体的解析就会回调valueResolver(根据不同策略创建的StringValueResolver对象),然后进入具体的解析,其解析过程已经分析,这里不在赘述。
总结
至此关于占位符的解析过程就大致分析完了,其实里面还有很多值得我们细究的地方,具体过程可debug调试一遍,可能会有更深的理解。
by Shawn Chen,2019.05.08,晚。
【spring源码分析】IOC容器初始化——查漏补缺(五)的更多相关文章
- 【spring源码分析】IOC容器初始化——查漏补缺(一)
前言:在[spring源码分析]IOC容器初始化(十一)中提到了初始化bean的三个步骤: 激活Aware方法. 后置处理器应用(before/after). 激活自定义的init方法. 这里我们就来 ...
- 【spring源码分析】IOC容器初始化——查漏补缺(二)
前言:在[spring源码分析]IOC容器初始化(八)中多次提到了前置处理与后置处理,本篇文章针对此问题进行分析.Spring对前置处理或后置处理主要通过BeanPostProcessor进行实现. ...
- 【spring源码分析】IOC容器初始化——查漏补缺(四)
前言:在前几篇查漏补缺中,其实我们已经涉及到bean生命周期了,本篇内容进行详细分析. 首先看bean实例化过程: 分析: bean实例化开始后 注入对象属性后(前面IOC初始化十几篇文章). 检查激 ...
- SPRING源码分析:IOC容器
在Spring中,最基本的IOC容器接口是BeanFactory - 这个接口为具体的IOC容器的实现作了最基本的功能规定 - 不管怎么着,作为IOC容器,这些接口你必须要满足应用程序的最基本要求: ...
- 【spring源码分析】IOC容器初始化——查漏补缺(三)
前言:本文分析InitializingBean和init-method方法,其实该知识点在AbstractAutowireCapableBeanFactory#initializeBean方法中有所提 ...
- Spring源码解析-ioc容器的设计
Spring源码解析-ioc容器的设计 1 IoC容器系列的设计:BeanFactory和ApplicatioContext 在Spring容器中,主要分为两个主要的容器系列,一个是实现BeanFac ...
- spring源码分析---IOC(1)
我们都知道spring有2个最重要的概念,IOC(控制反转)和AOP(依赖注入).今天我就分享一下spring源码的IOC. IOC的定义:直观的来说,就是由spring来负责控制对象的生命周期和对象 ...
- spring 源码之 ioc 容器的初始化和注入简图
IoC最核心就是两个过程:IoC容器初始化和IoC依赖注入,下面通过简单的图示来表述其中的关键过程:
- Spring源码阅读-IoC容器解析
目录 Spring IoC容器 ApplicationContext设计解析 BeanFactory ListableBeanFactory HierarchicalBeanFactory Messa ...
随机推荐
- 用D3js的区域生成器实现简单波浪图
最近做控件遇到含有波浪图的图表,一开始用Echarts虽然很快完成了,但Echarts的波浪图与其他图表的响应式不同步,于是学习了D3js,D3js写起来确实复杂一些,但能够实现的效果也更丰富,做的时 ...
- 在Eclipse配置Tomcat服务器+JSP实例创建
欢迎任何形式的转载,但请务必注明出处. 1.jdk安装及环境配置 点击进入教程 2.Eclipse安装 点击进入官网下载 注意下载完成打开.exe后,出现的界面,有很多版本供选择.选择下图版本 3.T ...
- 等保测评中与oracle有关的工作
等保2.0包含硬件.存储.中间件.数据库各方面的安全规范,现把与Oracle数据库有关的内容整理如下,供参考: 一.安全计算环境 1.身份鉴别: a,应对登陆的用户进行身份标识和鉴别,身份标识具有唯一 ...
- select加锁分析(Mysql)
[原创]惊!史上最全的select加锁分析(Mysql) 前言 大家在面试中有没遇到面试官问你下面六句Sql的区别呢 select * from table where id = ? select * ...
- 【robotframework】robotframework环境搭建
一.基于python3.6环境 在dos命令输入 pip install robotframework 在线安装robotframework在dos命令输入 pip install Pypubsub= ...
- VS代码自动排版
1, ctrl+a 2, ctrl+k 3, ctrl+f
- CentOS7下编译安装Python3.7.x【亲测有效】
所有操作都在root用户下操作 下载安装包 编译安装 建立软链接 验证 安装: 更新yum: yum update 安装Python依赖: yum install openssl-devel bzip ...
- JVM系列四:类加载
类的生命周期 加载->验证->准备->解析->初始化->使用->卸载 类加载过程 类加载包括以上的前五个过程:加载,验证,准备,解析,初始化 加载 1.主要完成三个 ...
- MySQL进阶19--函数的创建(举例)/设置mysql的创建函数的权限/查看(show)/删除(drop) / 举4个栗子
/*MySQL进阶19 函数 存储过程和函数:都类似于java中的方法; 存储过程和函数通用好处: 1.提高代码的重用性 2.简化操作 好处: 减少操作次数,减少了编译次数,减少了和服务器的连接次数, ...
- P1005 矩阵取数游戏[区间dp]
题目描述 帅帅经常跟同学玩一个矩阵取数游戏:对于一个给定的\(m*n\)的矩阵,矩阵中的每个元素\(a_{i,j}\)均为非负整数.游戏规则如下: 每次取数时须从每行各取走一个元素,共n个.经过m次后 ...