spring源码分析之配置文件名占位符的解析(一)
一、直接写个测试例子
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源码分析之配置文件名占位符的解析(一)的更多相关文章
- Spring源码分析(七)bean标签的解析及注册
摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 在上一篇中提到过Spring中的标签包括默认标签和自定义标签两种,而两种 ...
- 【Spring源码分析】Bean加载流程概览
代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...
- 【spring源码分析】IOC容器初始化(一)
前言:spring主要就是对bean进行管理,因此IOC容器的初始化过程非常重要,搞清楚其原理不管在实际生产或面试过程中都十分的有用.在[spring源码分析]准备工作中已经搭建好spring的环境, ...
- spring源码分析系列 (5) spring BeanFactoryPostProcessor拓展类PropertyPlaceholderConfigurer、PropertySourcesPlaceholderConfigurer解析
更多文章点击--spring源码分析系列 主要分析内容: 1.拓展类简述: 拓展类使用demo和自定义替换符号 2.继承图UML解析和源码分析 (源码基于spring 5.1.3.RELEASE分析) ...
- 【Spring源码分析】Bean加载流程概览(转)
转载自:https://www.cnblogs.com/xrq730/p/6285358.html 代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. ...
- 【spring源码分析】IOC容器初始化——查漏补缺(五)
前言:我们知道在Spring中经常使用配置文件的形式对进行属性的赋值,那配置文件的值是怎么赋值到属性上的呢,本文将对其进行分析. 首先了解一个类:PropertySourcesPlaceholderC ...
- Spring源码分析-BeanFactoryPostProcessors 应用之 PropertyPlaceholderConfigurer
BeanFactoryPostProcessors 介绍 BeanFactoryPostProcessors完整定义: /** * Allows for custom modification of ...
- spring源码分析(二)Aop
创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ...
- 【Spring源码分析】非懒加载的单例Bean初始化过程(下篇)
doCreateBean方法 上文[Spring源码分析]非懒加载的单例Bean初始化过程(上篇),分析了单例的Bean初始化流程,并跟踪代码进入了主流程,看到了Bean是如何被实例化出来的.先贴一下 ...
随机推荐
- 【转】Linux下添加FTP账号和服务器、增加密码和用户,更改FTP目录
转自:http://blog.csdn.net/cloudday/article/details/8640234 1. 启动VSFTP服务器 A:cenos下运行:yum install vs ...
- 【练习题】proj2 字符串压缩
输入一个字符串,输出简单的压缩 1)单字符串压缩 : 输入:ABBBCCD , 输出AB3C2D 2)多字符串压缩 输入:AABCABCD,输出A(ABC)2D 1)压缩单个字符 #include & ...
- 【Go】使用压缩文件优化io (一)
原文连接:https://blog.thinkeridea.com/201906/go/compress_file_io_optimization1.html 最近遇到一个日志备份 io 过高的问题, ...
- C语言实现Linux网络嗅探器
C语言实现Linux网络嗅探器 0x01 实验简介 网络嗅探器是拦截通过网络接口流入和流出的数据的程序.所以,如果你正在浏览的互联网,嗅探器以数据包的形式抓到它并且显示.在本实验中,我们用 C 语言实 ...
- 【工具】读取proprtties工具类
获取properties内容: 基本的使用看网络上大多是这样的,使用时注意线程安全以及读写的实时性问题. 1.直接通过流读取(反射): InputStream inStream = this.get ...
- 【死磕线程】线程同步机制_java多线程之线程锁
1.线程各种状态间的切换,用图表示的话简单清晰: 图出处:https://www.cnblogs.com/bhlsheji/p/5099362.html(博主对每个状态解释的清晰明了) 2.为什么需要 ...
- Linux下多网卡绑定bond及模式介绍
[介绍] 网卡bond一般主要用于网络吞吐量很大,以及对于网络稳定性要求较高的场景. 主要是通过将多个物理网卡绑定到一个逻辑网卡上,实现了本地网卡的冗余,带宽扩容以及负载均衡. Linux下一共有七种 ...
- Go - Slice 切片
概述 切片是一种动态数组,比数组操作灵活,长度不是固定的,可以进行追加和删除. len() 和 cap() 返回结果可相同和不同. 声明切片 //demo_7.go package main impo ...
- 网络虚拟化基础协议·Geneve
[分层] 要实现网络虚拟化,最基础的技术肯定是分层(OverLay & UnderLay). ·UnderLay 中文释义中,老房子漏雨,在房子里面撑一把大雨伞,这把大雨伞就是UnderLay ...
- zookeeper的客户端应用
什么zookeeper? ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件.它是一个为分布式应用提供 ...