一、结构类图

①、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源码解析(一)---占位符解析替换相关推荐

  1. spring 源码分析(1)-xml文件解析

    我们在最开始接触spring的时候,看到不少书spring入门的例子如下 ApplicationContext atx = new ClassPathXmlApplicationContext(&qu ...

  2. spring源码分析06-spring配置类解析

    什么是spring配置类? 类上有注解:@Configuration .@Component.@ComponentScan.@Import.@ImportResource 或者类中的任意方法有@Bea ...

  3. Spring源码分析(十)依赖注入源码解析3:DefaultListableBeanFactory#doResolveDependency 真正开始解析依赖项

    4.2 真正开始解析依赖项(最核心方法) org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveD ...

  4. Spring源码分析之Bean的创建过程详解

    前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...

  5. 【Spring源码】4. 自己搞个标签?~自定义标签保姆级全过程(图解向,堆图预警)

    [Spring源码系列- IOC] 1 [Spring源码]0.安装Gradle环境 2 [Spring源码]1.下载与编译_pom relocation to an other version nu ...

  6. Spring 源码第三弹!EntityResolver 是个什么鬼?

    上篇文章和小伙伴们说了 Spring 源码中 XML 文件的解析流程,本来可以继续往下走看加载核心类了,但是松哥还是希望能够慢一点,既然要学就学懂,在 XML 文件解析的过程中还涉及到一些其他的类和概 ...

  7. beaninfo详解源码解析 java_【Spring源码分析】Bean加载流程概览

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

  8. Spring源码解析【完整版】--【bilibili地址:https://www.bilibili.com/video/BV1oW41167AV】

    [本文为bilibili视频雷丰阳的Spring源码解析的完整版总结文章,其中文章前面大部分为他人博文的搬运,后面补充了其未总结的部分] 一.Java的注解 1. 注解的概念 注释:用文字描述程序,给 ...

  9. 【Java】【系列篇】【Spring源码解析】【三】【体系】【BeanFactory体系】

    BeanFactory体系 BeanFactory整体结构体系图 顶层接口-BeanFactory 1.1.描述 1.2.方法解析(15个) 1.2.1.属性 1.2.2.获取bean实例 1.2.3 ...

  10. spring源码分析02-spring生命周期源码解析

    spring生命周期流程图: 1.spring扫描 Spring最重要的功能就是帮助程序员创建对象(也就是IOC),而启动Spring就是为创建Bean对象 做准备,所以我们先明白Spring到底是怎 ...

最新文章

  1. springboot 集成mybatis时日志输出
  2. 别看不起分区表:我要为你点个赞
  3. python将数据存入数据库_Python读取NGINX日志将其存入数据库
  4. 用户激励体系搭建指南
  5. 设置api密钥_我应该将我的API密钥设置多长时间?
  6. 商城GW-SHOP,基于 微信小程序 + springboot + vue 技术构建
  7. Javassist学习文档
  8. MKS-DLC雕刻MKS_TFT_CNC字机器,CNC雕刻,激光雕刻GRBL使用方法
  9. gitlab使用教程
  10. cad批量选择相同块_在CAD中如何快速选择相同或类似的图形、图块?
  11. 数据库实战入门——SQL全方位学习
  12. 【Stats】Jarque Bera test正态性检验
  13. Java—核心技术类的封装、继承与多态
  14. DVWA-Writeup
  15. matlab vrp 线性规划,VRP算法学习
  16. java生成短网址_腾讯短链接url生成接口_url短网址生成
  17. 在PPT中批量导入图片
  18. pinyin4j 中文转成拼音(支持多音字输出)
  19. 换服务器影响网站排名,网站更换服务器空间会影响排名吗
  20. 世界科学、技术、工业革命趋势分析

热门文章

  1. sql语句分页多种方式ROW_NUMBER()OVER
  2. Table控件布局DataList模板
  3. 你可能不知道的shell、bash二三事(Centos 7)
  4. 提取已有的内核配置文件
  5. js href的用法
  6. Csharp四种简单的排序算法
  7. 如何创建和使用文档库 - [MOSS 2007应用日记]
  8. 解决asp数据库对象只读的办法
  9. oracle的简单命令
  10. 案例:回归分析-R实现