Spring PropertyResolver 占位符解析(二)源码分析

Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html)

Spring 3.1 提供了新的占位符解析器 PropertyResolver,默认实现为 PropertySourcesPropertyResolver。相关文章如下:

  1. Spring PropertyResolver 占位符解析(一)API 介绍
  2. Spring PropertyResolver 占位符解析(二)源码分析

一、PropertyResolver 接口

PropertyResolver 的默认实现是 PropertySourcesPropertyResolver,Environment 实际上也是委托 PropertySourcesPropertyResolver 完成 占位符的解析和类型转换。 类型转换又是委托 ConversionService 完成的。

  1. public interface PropertyResolver {
  2. // 1. contains
  3. boolean containsProperty(String key);
  4. // 2.1 获取指定 key,不存在可以指定默认值,也可以抛出异常
  5. String getProperty(String key);
  6. String getProperty(String key, String defaultValue);
  7. // 2.2 类型转换,委托 ConversionService 完成
  8. <T> T getProperty(String key, Class<T> targetType);
  9. <T> T getProperty(String key, Class<T> targetType, T defaultValue);
  10. String getRequiredProperty(String key) throws IllegalStateException;
  11. <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
  12. // 3. 解析占位符 ${key}
  13. String resolvePlaceholders(String text);
  14. String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
  15. }

ConfigurablePropertyResolver 是配置了一些解析占位符的必要属性,如占位符前缀和后缀等

  1. public interface ConfigurablePropertyResolver extends PropertyResolver {
  2. // 1. 类型转换
  3. ConfigurableConversionService getConversionService();
  4. void setConversionService(ConfigurableConversionService conversionService);
  5. // 2.1 ${} 分隔符
  6. void setPlaceholderPrefix(String placeholderPrefix);
  7. void setPlaceholderSuffix(String placeholderSuffix);
  8. // 2.2 默认属性分隔符 :
  9. void setValueSeparator(@Nullable String valueSeparator);
  10. // 3.1 ${key} getProperty(key)==null 时是否忽略,不抛出异常
  11. void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);
  12. void setRequiredProperties(String... requiredProperties);
  13. void validateRequiredProperties() throws MissingRequiredPropertiesException;
  14. }

二、PropertySourcesPropertyResolver 源码分析

PropertySourcesPropertyResolver 持有一个数据源 PropertySources,可以通过 getProperty 获取对应的属性值,这方法有几种重载的方法,决定是否解析嵌套占位符和类型转换。

  1. // 不进行类型转换,但会进行嵌套占位符的解析
  2. @Override
  3. public String getProperty(String key) {
  4. return getProperty(key, String.class, true);
  5. }
  6. // 进行类型转换,也进行嵌套占位符的解析
  7. @Override
  8. public <T> T getProperty(String key, Class<T> targetValueType) {
  9. return getProperty(key, targetValueType, true);
  10. }
  11. // 不进行类型转换,也不进行嵌套占位符的解析,返回原始的字符串
  12. @Override
  13. protected String getPropertyAsRawString(String key) {
  14. return getProperty(key, String.class, false);
  15. }

我们再看一下 getProperty(key, String.class, false) 这个方法

  1. protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
  2. if (this.propertySources != null) {
  3. for (PropertySource<?> propertySource : this.propertySources) {
  4. // 1. 从数据源中获取属性值
  5. Object value = propertySource.getProperty(key);
  6. if (value != null) {
  7. // 2. 如果属性值本身又含有占位符就属于嵌套占位符解析,如 ${a${x}b}
  8. if (resolveNestedPlaceholders && value instanceof String) {
  9. value = resolveNestedPlaceholders((String) value);
  10. }
  11. // 日志输出
  12. logKeyFound(key, propertySource, value);
  13. // 3. 类型转换
  14. return convertValueIfNecessary(value, targetValueType);
  15. }
  16. }
  17. }
  18. return null;
  19. }

现在最关键的方法是 resolveNestedPlaceholders,用于解析嵌套的占位符,这个方法是在其父类 AbstractPropertyResolver 实现的。

  1. protected String resolveNestedPlaceholders(String value) {
  2. return (this.ignoreUnresolvableNestedPlaceholders ?
  3. resolvePlaceholders(value) : resolveRequiredPlaceholders(value));
  4. }
  5. private PropertyPlaceholderHelper nonStrictHelper;
  6. private PropertyPlaceholderHelper strictHelper;
  7. @Override
  8. public String resolvePlaceholders(String text) {
  9. if (this.nonStrictHelper == null) {
  10. this.nonStrictHelper = createPlaceholderHelper(true);
  11. }
  12. return doResolvePlaceholders(text, this.nonStrictHelper);
  13. }
  14. @Override
  15. public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
  16. if (this.strictHelper == null) {
  17. this.strictHelper = createPlaceholderHelper(false);
  18. }
  19. return doResolvePlaceholders(text, this.strictHelper);
  20. }

实际上嵌套占位符的解析 PropertySourcesPropertyResolver 都委托给了 PropertyPlaceholderHelper 方法来完成,而自身主要完成从 PropertySources 获取属性值。

  1. private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
  2. return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
  3. this.valueSeparator, ignoreUnresolvablePlaceholders);
  4. }
  5. private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
  6. return helper.replacePlaceholders(text, this::getPropertyAsRawString);
  7. }

三、PropertyPlaceholderHelper 嵌套占位符的解析

在看 PropertyPlaceholderHelper 之前先看一下 PlaceholderResolver 这个内部类,这个类用于获取占位符 key 对应的 value。在 AbstractPropertyResolver#doResolvePlaceholders 方法中将 this::getPropertyAsRawString 传过来了,也就是说 PlaceholderResolver 是从 propertySources 获取对应的 value 值。

  1. @FunctionalInterface
  2. public interface PlaceholderResolver {
  3. // 从 propertySource 中获取 placeholderName 的 value
  4. String resolvePlaceholder(String placeholderName);
  5. }

下面再看 replacePlaceholders 是如何解析嵌套的占位符的。

  1. public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
  2. Assert.notNull(value, "'value' must not be null");
  3. // placeholderResolver 是从 propertySources 获取的属性值
  4. return parseStringValue(value, placeholderResolver, new HashSet<>());
  5. }
  6. // 循环解析 key ${a${x}b}
  7. protected String parseStringValue(
  8. String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
  9. StringBuilder result = new StringBuilder(value);
  10. int startIndex = value.indexOf(this.placeholderPrefix);
  11. while (startIndex != -1) {
  12. // 找到结束的 } 位置,注意嵌套时要找对应的结束标记符 ${a${x}b}
  13. int endIndex = findPlaceholderEndIndex(result, startIndex);
  14. // endIndex=-1 或 startIndex=-1 结束循环
  15. if (endIndex != -1) {
  16. // 1. 获取 ${key} 的 key
  17. String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
  18. String originalPlaceholder = placeholder;
  19. // 2. key 出现了循环嵌套,直接 Game Over
  20. if (!visitedPlaceholders.add(originalPlaceholder)) {
  21. throw new IllegalArgumentException(
  22. "Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
  23. }
  24. // 2. 循环解析这个 key,如果这个 key 又是形如 ${...}
  25. placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
  26. // 3. 至此,这个 key 不可能出现 ${} 了,因此可以放心大胆的从 propertySources 获取对应的 value
  27. String propVal = placeholderResolver.resolvePlaceholder(placeholder);
  28. // 4. ${key:default} 如果为 null,获取真正的 key,如果为 null 则为默认值
  29. if (propVal == null && this.valueSeparator != null) {
  30. int separatorIndex = placeholder.indexOf(this.valueSeparator);
  31. if (separatorIndex != -1) {
  32. String actualPlaceholder = placeholder.substring(0, separatorIndex);
  33. String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
  34. propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
  35. if (propVal == null) {
  36. propVal = defaultValue;
  37. }
  38. }
  39. }
  40. // 5. 对不起,value 也可能为 ${...},递归解析
  41. if (propVal != null) {
  42. propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
  43. result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
  44. startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
  45. } else if (this.ignoreUnresolvablePlaceholders) {
  46. // 6. 忽略无法解析的 key,继续...
  47. startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
  48. } else {
  49. throw new IllegalArgumentException("Could not resolve placeholder '" +
  50. placeholder + "'" + " in value \"" + value + "\"");
  51. }
  52. visitedPlaceholders.remove(originalPlaceholder);
  53. } else {
  54. startIndex = -1;
  55. }
  56. }
  57. return result.toString();
  58. }

每天用心记录一点点。内容也许不重要,但习惯很重要!

Spring PropertyResolver 占位符解析(二)源码分析的更多相关文章

  1. Spring PropertyResolver 占位符解析(一)API 介绍

    Spring PropertyResolver 占位符解析(一)API 介绍 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html ...

  2. Spring注解之@Lazy注解,源码分析和总结

    一 关于延迟加载的问题,有次和大神讨论他会不会直接或间接影响其他类.spring的好处就是文档都在代码里,网上百度大多是无用功. 不如,直接看源码.所以把当时源码分析的思路丢上来一波. 二 源码分析 ...

  3. Spring第四天,BeanPostProcessor源码分析,彻底搞懂IOC注入及注解优先级问题!

  4. 涨姿势:Spring Boot 2.x 启动全过程源码分析

    目录 SpringApplication 实例 run 方法运行过程 总结 上篇<Spring Boot 2.x 启动全过程源码分析(一)入口类剖析>我们分析了 Spring Boot 入 ...

  5. Spring Boot 2.x 启动全过程源码分析

    Spring Boot 2.x 启动全过程源码分析 SpringApplication 实例 run 方法运行过程 上面分析了 SpringApplication 实例对象构造方法初始化过程,下面继续 ...

  6. Spring Boot Dubbo 应用启停源码分析

    作者:张乎兴 来源:Dubbo官方博客 背景介绍 Dubbo Spring Boot 工程致力于简化 Dubbo | grep tid | grep -v "daemon" tid ...

  7. Spring系列28:@Transactional事务源码分析

    本文内容 @Transactional事务使用 @EnableTransactionManagement 详解 @Transactional事务属性的解析 TransactionInterceptor ...

  8. Spring Boot 2.x 启动全过程源码分析(上)入口类剖析

    Spring Boot 的应用教程我们已经分享过很多了,今天来通过源码来分析下它的启动过程,探究下 Spring Boot 为什么这么简便的奥秘. 本篇基于 Spring Boot 2.0.3 版本进 ...

  9. django 之(二) --- 源码分析

    CBV类视图继承 CBV:继承自View:注册的时候使用的as_view() 入口 不能使用请求方法的名字作为参数的名字 只能接受已经存在的属性对应的参数 定义了一个view 创建了一个类视图对象 保 ...

随机推荐

  1. matlab stereo_gui立体标定

    http://www.vision.caltech.edu/bouguetj/calib_doc/index.html#examples 文档中举了几个例子,有关双目的是第5个, 这个例子展示了如何使 ...

  2. SAP 自定义进度条

    *&---------------------------------------------------------------------* *& Report ZCHENH028 ...

  3. SAP 000 客户端初始登录

    Solution 在SAP系统DB中删除账号SAP*,SAP系统会自动创建SAP*这个账号,然后初始密码是“PASS”,这样就获得Client 000 SAP*账号. Step by Step 以Or ...

  4. cdh 安装系列3--cdh manager 安装 cdh 6.01 版本

    安装前提是cdh manager 已经可以通过admin登录,管理台安装在192.168.20.163 一.安装自动TLS Setup Auto-TLS 1.ssh 192.168.20.163 2. ...

  5. xshell常用的快捷键

    删除ctrl + d      删除光标所在位置上的字符相当于VIM里x或者dlctrl + h      删除光标所在位置前的字符相当于VIM里hx或者dhctrl + k      删除光标后面所 ...

  6. SSRF攻击-运用gopher协议构造POST包--emmmm(http://10.112.68.215:10004/index.php?action=login)

        还是天枢的一道CTF题,启程!       分析题目,自己注册账户并登陆后,提示输入一个url,网站会自己运行查找网页信息.     emmmmm,很挑衅,网站就当作服务器,我们在url框中输 ...

  7. bootstrap-table 使用遇到的问题总结

    问题一:右上角button样式自定义 方法: //修改bootstrap-table右上角按钮样式 $(".table-box .columns-right button").re ...

  8. f5 2017.09.03故障

    1.下午14点50左右有同事反应epm等系统登录有问题.自测登录也是有同样的报错. 2.测试发现内部IP直接访问正常,但是访问f5的vip的方式访问不了.此时oa.邮件等系统也开始有同事发现故障. 3 ...

  9. swift - xcode - pod升级版本和降级版本

    1. 查看当前版本 pod --version 2.如果安装过pod,更新命令 新版 sudo gem install -n /usr/local/bin cocoapods --pre 旧版 sud ...

  10. elastic5.4安装错误解决

    首先,我们从官网下载:(官网:https://www.elastic.co/downloads/elasticsearch)(推荐下载deb或者rpm包,否则坑很多) 启动 (需要依赖java环境) ...