中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Spring Boot環境屬性占位符解析及類型轉換詳解

發布時間:2020-09-26 21:28:14 來源:腳本之家 閱讀:291 作者:throwable 欄目:編程語言

前提

前面寫過一篇關于Environment屬性加載的源碼分析和擴展,里面提到屬性的占位符解析和類型轉換是相對復雜的,這篇文章就是要分析和解讀這兩個復雜的問題。關于這兩個問題,選用一個比較復雜的參數處理方法PropertySourcesPropertyResolver#getProperty,解析占位符的時候依賴到

PropertySourcesPropertyResolver#getPropertyAsRawString:

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

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

屬性占位符解析

屬性占位符的解析方法是PropertySourcesPropertyResolver的父類AbstractPropertyResolver#resolveNestedPlaceholders:

protected String resolveNestedPlaceholders(String value) {
 return (this.ignoreUnresolvableNestedPlaceholders ?
  resolvePlaceholders(value) : resolveRequiredPlaceholders(value));
}

ignoreUnresolvableNestedPlaceholders屬性默認為false,可以通過AbstractEnvironment#setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders)設置,當此屬性被設置為true,解析屬性占位符失敗的時候(并且沒有為占位符配置默認值)不會拋出異常,返回屬性原樣字符串,否則會拋出IllegalArgumentException。我們這里只需要分析AbstractPropertyResolver#resolveRequiredPlaceholders:

//AbstractPropertyResolver中的屬性:
//ignoreUnresolvableNestedPlaceholders=true情況下創建的PropertyPlaceholderHelper實例
@Nullable
private PropertyPlaceholderHelper nonStrictHelper;

//ignoreUnresolvableNestedPlaceholders=false情況下創建的PropertyPlaceholderHelper實例
@Nullable
private PropertyPlaceholderHelper strictHelper;

//是否忽略無法處理的屬性占位符,這里是false,也就是遇到無法處理的屬性占位符且沒有默認值則拋出異常
private boolean ignoreUnresolvableNestedPlaceholders = false;

//屬性占位符前綴,這里是"${"
private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX;

//屬性占位符后綴,這里是"}"
private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX;

//屬性占位符解析失敗的時候配置默認值的分隔符,這里是":"
@Nullable
private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR;


public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
 if (this.strictHelper == null) {
  this.strictHelper = createPlaceholderHelper(false);
 }
 return doResolvePlaceholders(text, this.strictHelper);
}

//創建一個新的PropertyPlaceholderHelper實例,這里ignoreUnresolvablePlaceholders為false
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
 return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix, this.valueSeparator, ignoreUnresolvablePlaceholders);
}

//這里最終的解析工作委托到PropertyPlaceholderHelper#replacePlaceholders完成
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
 return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}

最終只需要分析PropertyPlaceholderHelper#replacePlaceholders,這里需要重點注意:

注意到這里的第一個參數text就是屬性值的源字符串,例如我們需要處理的屬性為myProperties: ${server.port}-${spring.application.name},這里的text就是${server.port}-${spring.application.name}。

replacePlaceholders方法的第二個參數placeholderResolver,這里比較巧妙,這里的方法引用this::getPropertyAsRawString相當于下面的代碼:

//PlaceholderResolver是一個函數式接口
@FunctionalInterface
public interface PlaceholderResolver {
 @Nullable
 String resolvePlaceholder(String placeholderName); 
}
//this::getPropertyAsRawString相當于下面的代碼
return new PlaceholderResolver(){
 
 @Override
 String resolvePlaceholder(String placeholderName){
  //這里調用到的是PropertySourcesPropertyResolver#getPropertyAsRawString,有點繞
  return getPropertyAsRawString(placeholderName);
 }
}  

接著看PropertyPlaceholderHelper#replacePlaceholders的源碼:

//基礎屬性
//占位符前綴,默認是"${"
private final String placeholderPrefix;
//占位符后綴,默認是"}"
private final String placeholderSuffix;
//簡單的占位符前綴,默認是"{",主要用于處理嵌套的占位符如${xxxxx.{yyyyy}}
private final String simplePrefix;

//默認值分隔符號,默認是":"
@Nullable
private final String valueSeparator;
//替換屬性占位符
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
 Assert.notNull(value, "'value' must not be null");
 return parseStringValue(value, placeholderResolver, new HashSet<>());
}

//遞歸解析帶占位符的屬性為字符串
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) {
   //提取第一個占位符中的原始字符串,如${server.port}->server.port
   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...
   // 遞歸調用完畢后,可以確定得到的字符串一定是不帶占位符,這個時候調用getPropertyAsRawString獲取key對應的字符串值
   String propVal = placeholderResolver.resolvePlaceholder(placeholder);
   // 如果字符串值為null,則進行默認值的解析,因為默認值有可能也使用了占位符,如${server.port:${server.port-2:8080}}
   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());
     // 這里是把默認值的表達式做一次解析,解析到null,則直接賦值為defaultValue
     propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
     if (propVal == null) {
      propVal = defaultValue;
     }
    }
   }
   // 上一步解析出來的值不為null,但是它有可能是一個帶占位符的值,所以后面對值進行遞歸解析
   if (propVal != null) {
    // Recursive invocation, parsing placeholders contained in the
    // previously resolved placeholder value.
    propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
    // 這一步很重要,替換掉第一個被解析完畢的占位符屬性,例如${server.port}-${spring.application.name} -> 9090--${spring.application.name}
    result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
    if (logger.isTraceEnabled()) {
     logger.trace("Resolved placeholder '" + placeholder + "'");
    }
    // 重置startIndex為下一個需要解析的占位符前綴的索引,可能為-1,說明解析結束
    startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
   }
   else if (this.ignoreUnresolvablePlaceholders) {
    // 如果propVal為null并且ignoreUnresolvablePlaceholders設置為true,直接返回當前的占位符之間的原始字符串尾的索引,也就是跳過解析
    // Proceed with unprocessed value.
    startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
   }
   else {
    // 如果propVal為null并且ignoreUnresolvablePlaceholders設置為false,拋出異常
    throw new IllegalArgumentException("Could not resolve placeholder '" +
       placeholder + "'" + " in value \"" + value + "\"");
   }
   // 遞歸結束移除判重集合中的元素
   visitedPlaceholders.remove(originalPlaceholder);
  }
  else {
   // endIndex = -1說明解析結束
   startIndex = -1;
  }
 }
 return result.toString();
}

//基于傳入的起始索引,搜索第一個占位符后綴的索引,兼容嵌套的占位符
private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
 //這里index實際上就是實際需要解析的屬性的第一個字符,如${server.port},這里index指向s
 int index = startIndex + this.placeholderPrefix.length();
 int withinNestedPlaceholder = 0;
 while (index < buf.length()) {
  //index指向"}",說明有可能到達占位符尾部或者嵌套占位符尾部
  if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) {
   //存在嵌套占位符,則返回字符串中占位符后綴的索引值
   if (withinNestedPlaceholder > 0) {
    withinNestedPlaceholder--;
    index = index + this.placeholderSuffix.length();
   }
   else {
    //不存在嵌套占位符,直接返回占位符尾部索引
    return index;
   }
  }
  //index指向"{",記錄嵌套占位符個數withinNestedPlaceholder加1,index更新為嵌套屬性的第一個字符的索引
  else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) {
   withinNestedPlaceholder++;
   index = index + this.simplePrefix.length();
  }
  else {
   //index不是"{"或者"}",則進行自增
   index++;
  }
 }
 //這里說明解析索引已經超出了原字符串
 return -1;
}

//StringUtils#substringMatch,此方法會檢查原始字符串str的index位置開始是否和子字符串substring完全匹配
public static boolean substringMatch(CharSequence str, int index, CharSequence substring) {
 if (index + substring.length() > str.length()) {
  return false;
 }
 for (int i = 0; i < substring.length(); i++) {
  if (str.charAt(index + i) != substring.charAt(i)) {
   return false;
  }
 }
 return true;
}

上面的過程相對比較復雜,因為用到了遞歸,我們舉個實際的例子說明一下整個解析過程,例如我們使用了四個屬性項,我們的目標是獲取server.desc的值:

application.name=spring
server.port=9090
spring.application.name=${application.name}
server.desc=${server.port-${spring.application.name}}:${description:"hello"}

Spring Boot環境屬性占位符解析及類型轉換詳解

屬性類型轉換

在上一步解析屬性占位符完畢之后,得到的是屬性字符串值,可以把字符串轉換為指定的類型,此功能由AbstractPropertyResolver#convertValueIfNecessary完成:

protected <T> T convertValueIfNecessary(Object value, @Nullable Class<T> targetType) {
 if (targetType == null) {
  return (T) value;
 }
 ConversionService conversionServiceToUse = this.conversionService;
 if (conversionServiceToUse == null) {
  // Avoid initialization of shared DefaultConversionService if
  // no standard type conversion is needed in the first place...
  // 這里一般只有字符串類型才會命中
  if (ClassUtils.isAssignableValue(targetType, value)) {
   return (T) value;
  }
  conversionServiceToUse = DefaultConversionService.getSharedInstance();
 }
 return conversionServiceToUse.convert(value, targetType);
}

實際上轉換的邏輯是委托到DefaultConversionService的父類方法GenericConversionService#convert:

public <T> T convert(@Nullable Object source, Class<T> targetType) {
 Assert.notNull(targetType, "Target type to convert to cannot be null");
 return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType));
}

public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
 Assert.notNull(targetType, "Target type to convert to cannot be null");
 if (sourceType == null) {
  Assert.isTrue(source == null, "Source must be [null] if source type == [null]");
  return handleResult(null, targetType, convertNullSource(null, targetType));
 }
 if (source != null && !sourceType.getObjectType().isInstance(source)) {
  throw new IllegalArgumentException("Source to convert from must be an instance of [" +
     sourceType + "]; instead it was a [" + source.getClass().getName() + "]");
 }
 // 從緩存中獲取GenericConverter實例,其實這一步相對復雜,匹配兩個類型的時候,會解析整個類的層次進行對比
 GenericConverter converter = getConverter(sourceType, targetType);
 if (converter != null) {
  // 實際上就是調用轉換方法
  Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
  // 斷言最終結果和指定類型是否匹配并且返回
  return handleResult(sourceType, targetType, result);
 }
 return handleConverterNotFound(source, sourceType, targetType);
}

上面所有的可用的GenericConverter的實例可以在DefaultConversionService的addDefaultConverters中看到,默認添加的轉換器實例已經超過20個,有些情況下如果無法滿足需求可以添加自定義的轉換器,實現GenericConverter接口添加進去即可。

小結

SpringBoot在抽象整個類型轉換器方面做的比較好,在SpringMVC應用中,采用的是org.springframework.boot.autoconfigure.web.format.WebConversionService,兼容了Converter、Formatter、ConversionService等轉換器類型并且對外提供一套統一的轉換方法。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

青冈县| 鸡泽县| 浦北县| 兴和县| 时尚| 和田市| 鲁甸县| 太仆寺旗| 岐山县| 黄梅县| 大同县| 鄂温| 荥阳市| 库伦旗| 射洪县| 昆山市| 铜川市| 绩溪县| 南宁市| 嘉黎县| 乐东| 阿巴嘎旗| 怀远县| 平度市| 芦溪县| 恩平市| 毕节市| 凤凰县| 横峰县| 霍林郭勒市| 星子县| 司法| 甘肃省| 左权县| 类乌齐县| 宁陕县| 南漳县| 西乌珠穆沁旗| 阿城市| 仁布县| 池州市|