一、直接写个测试例子

package com.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; import com.test.controller.User; public class UserTest { @Test
public void test() { @SuppressWarnings("resource")
ApplicationContext beanFactory = new ClassPathXmlApplicationContext("classpath:springContext.xml"); User user = beanFactory.getBean("user", User.class); String username = user.getName(); System.out.println(username); } }

二、直接debug运行

在进入代码之前,先了解一下这个ClassPathXmlApplicationContext类的继承关系

1、首先进入

//这里的configLocation的值就是classpath:springContext.xml
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}

2、继续进入ClassPathXmlApplicationContext的构造器

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException { super(parent);//parent为null值
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}

3、进入setConfigLocation这个方法定义于父类AbstractRefreshableConfigApplicationContext

public void setConfigLocations(String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
//new一个String数组,用来装解析后的配置文件地址
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}

4、继续进入resolvePath方法,它这里创建了一个StandEnvironment实例,这个实例包含着一些系统参数,环境变量参数,不过它现在还没有做任何事情,仅仅是创建它的一个实例。后面用到再说。

protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}

5.进入getEnvironment方法,这个方法定义于父类AbstractApplicationContext。

@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}

6、接下来调用StandEnvironment的resolveRequiredPlaceholders(path)方法,先看看StrandEnvironment结构。

这里的resolveRequiredPlaceholders方法定义在父类AbstractEnvironment中,这个方法的代码如下:

//注意这个类的实例被注入到了PropertySourcesPropertyResolver中了,等下会用到
private final MutablePropertySources propertySources = new MutablePropertySources(this.logger); private final ConfigurablePropertyResolver propertyResolver =
new PropertySourcesPropertyResolver(this.propertySources); public AbstractEnvironment() {
customizePropertySources(this.propertySources);
if (this.logger.isDebugEnabled()) {
this.logger.debug(format(
"Initialized %s with PropertySources %s", getClass().getSimpleName(), this.propertySources));
}
} @Override//这个方法是实现于StandardEnvironment类中,我把它放到了一起
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); //这个getSystemProperties调用的是System.getProperties()
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));//获得系统变量 } @Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
// 这个propertyResolver在new出StandardEnvironment的时候就被创建了
return this.propertyResolver.resolveRequiredPlaceholders(text);
}

7、进入PropertySourcesPropertyResolver 类的resolveRequiredPlaceholders方法,这个方法存在于PropertySourcesPropertyResolver的父类AbstractPropertyResolver里面

@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (this.strictHelper == null) {
//这里创建了一个占位符助手实例,这个false参数表示忽略不能解析的占位符
this.strictHelper = createPlaceholderHelper(false);
}
//这里才是真正解析的开始
return doResolvePlaceholders(text, this.strictHelper);
}

8、这个PlaceholderHelper在创建时有一段静态块的初始化

private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<String, String>(4);

    static {
wellKnownSimplePrefixes.put("}", "{");
wellKnownSimplePrefixes.put("]", "[");
wellKnownSimplePrefixes.put(")", "(");
}

9、进入doResolvePlaceholders方法

private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
//调用了属性占位符助手的替换占位符的方法
return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
@Override
public String resolvePlaceholder(String placeholderName) {
return getPropertyAsRawString(placeholderName);
}
});
}

10、进到PropertyPlaceholderHelper类的replacePlaceholders方法,strVal就是我们从前面传进来的配置文件的名称:classpath:springContext.xml,placeholderResolver可以通过占位符找到对应的值,怎么找的,后面再说。这段代码非常有用,也许有一天会对你有用。

protected String parseStringValue(
String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) { StringBuilder result = new StringBuilder(strVal);
//检查这个字符串时候有 ${ 前缀
int startIndex = strVal.indexOf(this.placeholderPrefix);
while (startIndex != -1) {
//如果有 ${ 前缀,再检查是否有 } 后缀
int endIndex = findPlaceholderEndIndex(result, startIndex);
if (endIndex != -1) {
//拿到占位符,如classpath:spring${key}.xml,这个占位符是key
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
String originalPlaceholder = placeholder;
//将当前的占位符存到set集合中,如果set集合有了,就会添加失败
//就会报错,循环引用错误,比如${a},这个a的值依然是${a}
//这样就陷入了无限解析了,根本停不下来
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}
// Recursive invocation, parsing placeholders contained in the placeholder key. //对占位符进行解析,如:${${a}},所以要继续解析
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// Now obtain the value for the fully resolved key...
//调用这个解析器查找占位符对应的值,这个方法的代码在下面11步给出
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
if (propVal == null && this.valueSeparator != null) {
//如果为null,那么查找这个propVal是否为:分割的字符串
int separatorIndex = placeholder.indexOf(this.valueSeparator);
if (separatorIndex != -1) {
//如果propVal为key:Context,那么这个值应为key
String actualPlaceholder = placeholder.substring(0, separatorIndex);
//如果propVal为key:Context,那么就是Context
String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
//跟上面的一样去系统属性中查找
propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
//如果为空,那么就设置为defaultValue,如key:Context
//defaultValue = Context;
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());
}
//如果propValue为null,那么就说明这个占位符没有值,如果设置为忽略
//不能解析的占位符,那么继续后续的占位符,否则报错
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 string value \"" + strVal + "\"");
}
//解析成功就删除set集合中对应的占位符
visitedPlaceholders.remove(originalPlaceholder);
}
else {
startIndex = -1;
}
} return result.toString();
}

11、resolvePlaceholder方法调用了getPropertyAsRawString方法,这个方法又调用了PropertySourcesPropertyResolver类的getProperty方法

//参数说明,key是传进来的占位符,targetValueType指的是目标类型,这里肯定是String.class, resolveNestedPlaceholders表示是否要对嵌套的占位符进行解析,这里传的是false,不需要。
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
boolean debugEnabled = logger.isDebugEnabled();
if (logger.isTraceEnabled()) {
logger.trace(String.format("getProperty(\"%s\", %s)", key, targetValueType.getSimpleName()));
}
//这个类就是在AstractEnvironment中传进来的MutablePropertySource 实例,上面第6点已经说它是怎么进来的,它存有系统属性和系统环境变量
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
if (debugEnabled) {
logger.debug(String.format("Searching for key '%s' in [%s]", key, propertySource.getName()));
}
//从系统属性中寻找值
Object value = propertySource.getProperty(key);
if (value != null) {
Class<?> valueType = value.getClass();
//如果允许解析嵌入的${},并且是String类型的就继续解析
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
if (debugEnabled) {
logger.debug(String.format("Found key '%s' in [%s] with type [%s] and value '%s'",
key, propertySource.getName(), valueType.getSimpleName(), value));
}
//判断当前值得类型能够转换成目标类型,如果不能,直接报错
if (!this.conversionService.canConvert(valueType, targetValueType)) {
throw new IllegalArgumentException(String.format(
"Cannot convert value [%s] from source type [%s] to target type [%s]",
value, valueType.getSimpleName(), targetValueType.getSimpleName()));
}
//如果可以转换直接转换返回这个值
return this.conversionService.convert(value, targetValueType);
}
}
}
if (debugEnabled) {
logger.debug(String.format("Could not find key '%s' in any property source. Returning [null]", key));
}
//如果在系统属性中没有得到值,那么返回null值。
return null;
}

12、最后返回到AbstractRefreshableConfigApplicationContext类,将解析后的配置文件路径设置到configLocations属性中

public void setConfigLocations(String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}

总结

spring源码分析之配置文件名占位符的解析(一)的更多相关文章

  1. Spring源码分析(七)bean标签的解析及注册

    摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 在上一篇中提到过Spring中的标签包括默认标签和自定义标签两种,而两种 ...

  2. 【Spring源码分析】Bean加载流程概览

    代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...

  3. 【spring源码分析】IOC容器初始化(一)

    前言:spring主要就是对bean进行管理,因此IOC容器的初始化过程非常重要,搞清楚其原理不管在实际生产或面试过程中都十分的有用.在[spring源码分析]准备工作中已经搭建好spring的环境, ...

  4. spring源码分析系列 (5) spring BeanFactoryPostProcessor拓展类PropertyPlaceholderConfigurer、PropertySourcesPlaceholderConfigurer解析

    更多文章点击--spring源码分析系列 主要分析内容: 1.拓展类简述: 拓展类使用demo和自定义替换符号 2.继承图UML解析和源码分析 (源码基于spring 5.1.3.RELEASE分析) ...

  5. 【Spring源码分析】Bean加载流程概览(转)

    转载自:https://www.cnblogs.com/xrq730/p/6285358.html 代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. ...

  6. 【spring源码分析】IOC容器初始化——查漏补缺(五)

    前言:我们知道在Spring中经常使用配置文件的形式对进行属性的赋值,那配置文件的值是怎么赋值到属性上的呢,本文将对其进行分析. 首先了解一个类:PropertySourcesPlaceholderC ...

  7. Spring源码分析-BeanFactoryPostProcessors 应用之 PropertyPlaceholderConfigurer

    BeanFactoryPostProcessors 介绍 BeanFactoryPostProcessors完整定义: /** * Allows for custom modification of ...

  8. spring源码分析(二)Aop

    创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ...

  9. 【Spring源码分析】非懒加载的单例Bean初始化过程(下篇)

    doCreateBean方法 上文[Spring源码分析]非懒加载的单例Bean初始化过程(上篇),分析了单例的Bean初始化流程,并跟踪代码进入了主流程,看到了Bean是如何被实例化出来的.先贴一下 ...

随机推荐

  1. 关于Git 的管理凭据操作

    1.桌面-->2.我的电脑-->3.右击选择属性-->4.控制面板主页-->5.在用户账户和家庭安全下,选择添加或删除用户账户-->转到“主用户账户”页面-->6. ...

  2. Django之forms组件使用

    注册功能 1.渲染前端标签获取用户输入 >>> 渲染标签 2.获取用户输入传递到后端校验 >>> 校验数据 3.校验未通过展示错误信息 >>> 展 ...

  3. 大白话5分钟带你走进人工智能-第30节集成学习之Boosting方式和Adaboost

    目录 1.前述: 2.Bosting方式介绍: 3.Adaboost例子: 4.adaboost整体流程: 5.待解决问题: 6.解决第一个问题:如何获得不同的g(x): 6.1 我们看下权重与函数的 ...

  4. Hadoop初步学习

    我们老板理解的大数据是,从数据到知识的转化.大数据目前的应用如 支付宝金融大数据.腾讯出行大数据等. 大数据的工作就是从海量数据源中筛选,梳理对自己有用的数据,整合成合适的数据结构,存储并进行可视化. ...

  5. 安装mysql apache php smb

    1 SMB LinuX下SMB的配置 使用Smb进行连接的命令: smbclient //192.168.128.1/Share 今天要在LINUX之间以及LINUX与WINDOWS之间互相传送文件, ...

  6. Go - Map 集合

    目录 概述 声明 Map 生成 JSON 编辑和删除 推荐阅读 概述 Map 集合是无序的 key-value 数据结构. Map 集合中的 key / value 可以是任意类型,但所有的 key ...

  7. 常用的URL Scheme

    系统 短信 sms:// app store itms-apps:// 电话 tel:// 备忘录 mobilenotes:// 设置 prefs:root=SETTING E-Mail MESSAG ...

  8. ZOJ 3795:Grouping(缩点+最长路)

    http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5303 题意:有n个人m条边,每条边有一个u,v,代表u的年龄大于等于v,现在要 ...

  9. redis 文件事件模型

    参考文献: 深入剖析 redis 事件驱动 Redis 中的事件循环 深入了解epoll (转) Redis自己的事件模型 ae EPOLL(7) Linux IO模式及 select.poll.ep ...

  10. WebSocket+Netty构建web聊天程序

    WebSocket 传统的浏览器和服务器之间的交互模式是基于请求/响应的模式,虽然可以使用js发送定时任务让浏览器在服务器中拉取但是弊端很明显,首先就是不等避免的延迟,其次就是频繁的请求,让服务器的压 ...