spring源码解析(一)---占位符解析替换
一、结构类图
①、PropertyResolver : Environment的顶层接口,主要提供属性检索和解析带占位符的文本。bean.xml配置中的所有占位符例如${}都由它解析
②、ConfigurablePropertyResolver : 该接口定义了如何对组件本身进行配置。如:刚刚提到获取value时可以指定任意类型,这依赖于ConversionService进行类型转换,当前接口就提供了对ConversionService的设置和获取。另外,可以配置属性占位符的格式,包括:占位符前缀(默认为"${")、占位符后缀(默认为"}")、占位符值分隔符(默认为":",用于分隔propertyName和defaultValue)。组件还可以设置哪些属性是必须存在的,还可以校验必须存在的属性是否真的存在(不存在的话会抛出异常)
③、AbstractPropertyResolver : 实现了ConfigurablePropertyResolver接口的所有方法
④、PropertySourcesPropertyResolver : 以PropertySources属性源集合(内部持有属性源列表List<PropertySource>)为属性值的来源,按序遍历每个PropertySource,获取到一个非null的属性值则返回
二、demo示例
public static void main(String[] args) {Properties properties = System.getProperties();properties.setProperty("prefixName", "read-code");ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:${prefixName}-spring.xml");ReadCodeService readCodeService = (ReadCodeService) ac.getBean("readCodeService");readCodeService.say();}
View Code
三、源码剖析
1、入口 :
ClassPathXmlApplicationContext 构造函数setConfigLocations
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)throws BeansException {super(parent);setConfigLocations(configLocations);if (refresh) {refresh();}}
2、AbstractRefreshableConfigApplicationContext
①、ClassPathXmlApplicationContext构造函数调用它的基类AbstractRefreshableConfigApplicationContext.setConfigLocations
/*** Set the config locations for this application context.* <p>If not set, the implementation may use a default as appropriate.*/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;}}
②、解析路劲
/*** Resolve the given path, replacing placeholders with corresponding* environment property values if necessary. Applied to config locations.* @param path the original file path* @return the resolved file path* @see org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)*/protected String resolvePath(String path) {return getEnvironment().resolveRequiredPlaceholders(path);}
3、AbstractPropertyResolver
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {if (this.strictHelper == null) {this.strictHelper = createPlaceholderHelper(false);}return doResolvePlaceholders(text, this.strictHelper);}
上述方法主要做了两件事 :
①、初始化占位符解析器
createPlaceholderHelper : 主要是初始化占位符的常量,eg : 前缀 ${ 后缀} and so on
②、调用私有方法---替换占位符具体值
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {@Overridepublic String resolvePlaceholder(String placeholderName) {return getPropertyAsRawString(placeholderName);}});}
4、占位符 key - > value ,
实现PropertyPlaceholderHelper内部接口PlaceholderResolver方法resolvePlaceholder。找到占位符key对应的value,为下文替换key埋下伏笔
protected String getPropertyAsRawString(String key) {return getProperty(key, String.class, 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()));}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;if ((value = propertySource.getProperty(key)) != null) {Class<?> valueType = value.getClass();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));}return null;}
View Code
5、占位符解析器, 解析并替换具体值得逻辑在这里
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {Assert.notNull(value, "'value' must not be null");return parseStringValue(value, placeholderResolver, new HashSet<String>());}
递归查找占位符
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) {String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);String originalPlaceholder = placeholder;if (!visitedPlaceholders.add(originalPlaceholder)) {throw new IllegalArgumentException("Circular placeholder reference '" + originalPlaceholder + "' in property definitions");}// Recursive invocation, parsing placeholders contained in the placeholder key. placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); // 递归查找// Now obtain the value for the fully resolved key...String propVal = placeholderResolver.resolvePlaceholder(placeholder);if (propVal == null && this.valueSeparator != null) {int separatorIndex = placeholder.indexOf(this.valueSeparator);if (separatorIndex != -1) {String actualPlaceholder = placeholder.substring(0, separatorIndex);String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder); // 这里是调用第四步骤的实现PropertyPlaceholderHelper内部接口PlaceholderResolver方法resolvePlaceholder :占位符 key -> valueif (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());}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 + "\"");}visitedPlaceholders.remove(originalPlaceholder);}else {startIndex = -1;}}return result.toString();}
findPlaceholderEndIndex 查找占位符在所在字符串后缀的位置
private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {int index = startIndex + this.placeholderPrefix.length();int withinNestedPlaceholder = 0;while (index < buf.length()) {if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) {if (withinNestedPlaceholder > 0) {withinNestedPlaceholder--;index = index + this.placeholderSuffix.length();}else {return index;}}else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) {withinNestedPlaceholder++;index = index + this.simplePrefix.length();}else {index++;}}return -1;}
View Code
StringUtis.substringMatch 匹配当前位置的字符是否为占位符后缀
public static boolean substringMatch(CharSequence str, int index, CharSequence substring) {for (int j = 0; j < substring.length(); j++) {int i = index + j;if (i >= str.length() || str.charAt(i) != substring.charAt(j)) {return false;}}return true;}
View Code
转载于:https://www.cnblogs.com/chenmo-xpw/p/5575571.html
spring源码解析(一)---占位符解析替换相关推荐
- spring 源码分析(1)-xml文件解析
我们在最开始接触spring的时候,看到不少书spring入门的例子如下 ApplicationContext atx = new ClassPathXmlApplicationContext(&qu ...
- spring源码分析06-spring配置类解析
什么是spring配置类? 类上有注解:@Configuration .@Component.@ComponentScan.@Import.@ImportResource 或者类中的任意方法有@Bea ...
- Spring源码分析(十)依赖注入源码解析3:DefaultListableBeanFactory#doResolveDependency 真正开始解析依赖项
4.2 真正开始解析依赖项(最核心方法) org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveD ...
- Spring源码分析之Bean的创建过程详解
前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...
- 【Spring源码】4. 自己搞个标签?~自定义标签保姆级全过程(图解向,堆图预警)
[Spring源码系列- IOC] 1 [Spring源码]0.安装Gradle环境 2 [Spring源码]1.下载与编译_pom relocation to an other version nu ...
- Spring 源码第三弹!EntityResolver 是个什么鬼?
上篇文章和小伙伴们说了 Spring 源码中 XML 文件的解析流程,本来可以继续往下走看加载核心类了,但是松哥还是希望能够慢一点,既然要学就学懂,在 XML 文件解析的过程中还涉及到一些其他的类和概 ...
- beaninfo详解源码解析 java_【Spring源码分析】Bean加载流程概览
代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...
- Spring源码解析【完整版】--【bilibili地址:https://www.bilibili.com/video/BV1oW41167AV】
[本文为bilibili视频雷丰阳的Spring源码解析的完整版总结文章,其中文章前面大部分为他人博文的搬运,后面补充了其未总结的部分] 一.Java的注解 1. 注解的概念 注释:用文字描述程序,给 ...
- 【Java】【系列篇】【Spring源码解析】【三】【体系】【BeanFactory体系】
BeanFactory体系 BeanFactory整体结构体系图 顶层接口-BeanFactory 1.1.描述 1.2.方法解析(15个) 1.2.1.属性 1.2.2.获取bean实例 1.2.3 ...
- spring源码分析02-spring生命周期源码解析
spring生命周期流程图: 1.spring扫描 Spring最重要的功能就是帮助程序员创建对象(也就是IOC),而启动Spring就是为创建Bean对象 做准备,所以我们先明白Spring到底是怎 ...
最新文章
- springboot 集成mybatis时日志输出
- 别看不起分区表:我要为你点个赞
- python将数据存入数据库_Python读取NGINX日志将其存入数据库
- 用户激励体系搭建指南
- 设置api密钥_我应该将我的API密钥设置多长时间?
- 商城GW-SHOP,基于 微信小程序 + springboot + vue 技术构建
- Javassist学习文档
- MKS-DLC雕刻MKS_TFT_CNC字机器,CNC雕刻,激光雕刻GRBL使用方法
- gitlab使用教程
- cad批量选择相同块_在CAD中如何快速选择相同或类似的图形、图块?
- 数据库实战入门——SQL全方位学习
- 【Stats】Jarque Bera test正态性检验
- Java—核心技术类的封装、继承与多态
- DVWA-Writeup
- matlab vrp 线性规划,VRP算法学习
- java生成短网址_腾讯短链接url生成接口_url短网址生成
- 在PPT中批量导入图片
- pinyin4j 中文转成拼音(支持多音字输出)
- 换服务器影响网站排名,网站更换服务器空间会影响排名吗
- 世界科学、技术、工业革命趋势分析