2023-09-12  阅读(1)
原文作者:一直不懂 原文地址: https://blog.csdn.net/shenchaohao12321/article/details/80390457

PropertySourcesPropertyResolver用来将PropertySource的占位符文本解析,PropertyResolver是 Environment的顶层接口,主要提供属性检索和解析带占位符的文本。

202309122023074491.png

    public interface PropertyResolver {
       boolean containsProperty(String key);
       @Nullable
       String getProperty(String key);
       String getProperty(String key, String defaultValue);
       @Nullable
       <T> T getProperty(String key, Class<T> targetType);
       <T> T getProperty(String key, Class<T> targetType, T defaultValue);
       String getRequiredProperty(String key) throws IllegalStateException;
       <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
       String resolvePlaceholders(String text);
       String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
    }

ConfigurablePropertyResolver定了解析占位符的一些配置方法。

    public interface ConfigurablePropertyResolver extends PropertyResolver {
       ConfigurableConversionService getConversionService();
       void setConversionService(ConfigurableConversionService conversionService);
       void setPlaceholderPrefix(String placeholderPrefix);
       void setPlaceholderSuffix(String placeholderSuffix);
       void setValueSeparator(@Nullable String valueSeparator);
       void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);
       void setRequiredProperties(String... requiredProperties);
       void validateRequiredProperties() throws MissingRequiredPropertiesException;
    }

AbstractPropertyResolver封装了解析占位符的具体实现,而PropertySourcesPropertyResolver主要是负责提供数据源。AbstractPropertyResolver中有两个成员变量都是PropertyPlaceholderHelper对象,区别在于构造参数不同。

    public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix,
          @Nullable String valueSeparator, boolean ignoreUnresolvablePlaceholders) {
    
       Assert.notNull(placeholderPrefix, "'placeholderPrefix' must not be null");
       Assert.notNull(placeholderSuffix, "'placeholderSuffix' must not be null");
       this.placeholderPrefix = placeholderPrefix;
       this.placeholderSuffix = placeholderSuffix;
       String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix);
       if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) {
          this.simplePrefix = simplePrefixForSuffix;
       }
       else {
          this.simplePrefix = this.placeholderPrefix;
       }
       this.valueSeparator = valueSeparator;
       this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
    }

placeholderPrefix和placeholderSuffix是从AbstractPropertyResolver传过来的${和},代表占位符的边界符号。valueSeparator也是传过来的默认值分隔符“:”。ignoreUnresolvablePlaceholders是否忽略占位符中不存在的数据源,false会抛出IllegalArgumentException。

replacePlaceholders()方法用来将value中占位符替换为从Properties或PlaceholderResolver取得的值。

    public String replacePlaceholders(String value, final Properties properties) {
       Assert.notNull(properties, "'properties' must not be null");
       return replacePlaceholders(value, properties::getProperty);
    }
    public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
       Assert.notNull(value, "'value' must not be null");
       return parseStringValue(value, placeholderResolver, new HashSet<>());
    }

PlaceholderResolver是一个接口定了一个方法,入参为占位符参数名,出参为占位符代表的值。这个对象由PropertySourcesPropertyResolver传入,具体后面再说。具体的解析占位符就是通过parseStringValue()方法了:

    protected String parseStringValue( String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
       StringBuilder result = new StringBuilder(value);
       int startIndex = value.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);
                   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());
             }
             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 value \"" + value + "\"");
             }
             visitedPlaceholders.remove(originalPlaceholder);
          }
          else {
             startIndex = -1;
          }
       }
       return result.toString();
    }

这是一个递归的解析过程,遇到${开头就会查找最后一个}符号,将最外层占位符内的内容作为新的value再次传入 parseStringValue()方法中,这样最深层次也就是最先返回的就是最里层的占位符名字。调用placeholderResolver将占位符名字转换成它代表的值。如果值为null,则考虑使用默认值(valueSeparator后的内容)赋值给propVal。由于placeholderResolver转换过的值有可能还会包含占位符所以在此调用parseStringValue()方法将带有占位符的propVal传入返回真正的值,用propVal替换占位符。如果propVal==null,判断是否允许忽略不能解析的占位符,如果可以重置startIndex继续解析同一层次的占位符。否则抛出异常。这个函数的返回值就是它上一层次的占位符解析值。

需要注意的一点是:判断嵌套的占位符是依据simplePrefix。

    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;
    }

AbstractPropertyResolver解析占位符的方法就是委托了PropertyPlaceholderHelper对象的replacePlaceholders()方法,placeholderResolver参数由抽象方法getPropertyAsRawString()定义,子类给出具体实现。

    @Override
    public String resolvePlaceholders(String text) {
       if (this.nonStrictHelper == null) {
          this.nonStrictHelper = createPlaceholderHelper(true);
       }
       return doResolvePlaceholders(text, this.nonStrictHelper);
    }
    
    @Override
    public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
       if (this.strictHelper == null) {
          this.strictHelper = createPlaceholderHelper(false);
       }
       return doResolvePlaceholders(text, this.strictHelper);
    }
    private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
       return helper.replacePlaceholders(text, this::getPropertyAsRawString);
    }
    @Nullable
    protected abstract String getPropertyAsRawString(String key);

getProperty()方法也是子类给出的具体实现,下面看一下PropertySourcesPropertyResolver。

构造方法会传入一个PropertySources对象作为数据源。

    public PropertySourcesPropertyResolver(@Nullable PropertySources propertySources) {
       this.propertySources = propertySources;
    }

PropertyPlaceholderHelper需要的PlaceholderResolver函数方法。

    @Override
    @Nullable
    protected String getPropertyAsRawString(String key) {
       return getProperty(key, String.class, false);
    }

可以看出核心方法就是getProperty(),这里resolveNestedPlaceholders传入的是false,因为占位符解析是交给了PropertyPlaceholderHelper对象,不需要PropertySourcesPropertyResolver再次使用父类的resolveNestedPlaceholders再次解析占位符。

    @Nullable
    protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
       if (this.propertySources != null) {
          for (PropertySource<?> propertySource : this.propertySources) {
             if (logger.isTraceEnabled()) {
                logger.trace("Searching for key '" + key + "' in PropertySource '" +
                      propertySource.getName() + "'");
             }
             Object value = propertySource.getProperty(key);
             if (value != null) {
                if (resolveNestedPlaceholders && value instanceof String) {
                   value = resolveNestedPlaceholders((String) value);
                }
                logKeyFound(key, propertySource, value);
                return convertValueIfNecessary(value, targetValueType);
             }
          }
       }
       if (logger.isDebugEnabled()) {
          logger.debug("Could not find key '" + key + "' in any property source");
       }
       return null;
    }

而直接调用getProperty()方法就需要 resolveNestedPlaceholders=true了。

    @Override
    @Nullable
    public String getProperty(String key) {
       return getProperty(key, String.class, true);
    }
    
    @Override
    @Nullable
    public <T> T getProperty(String key, Class<T> targetValueType) {
       return getProperty(key, targetValueType, true);
    }

Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文