您好,登錄后才能下訂單哦!
前提
前面寫過一篇關(guān)于Environment屬性加載的源碼分析和擴(kuò)展,里面提到屬性的占位符解析和類型轉(zhuǎn)換是相對復(fù)雜的,這篇文章就是要分析和解讀這兩個(gè)復(fù)雜的問題。關(guān)于這兩個(gè)問題,選用一個(gè)比較復(fù)雜的參數(shù)處理方法PropertySourcesPropertyResolver#getProperty,解析占位符的時(shí)候依賴到
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); //需要時(shí)轉(zhuǎn)換屬性的類型 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屬性默認(rèn)為false,可以通過AbstractEnvironment#setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders)設(shè)置,當(dāng)此屬性被設(shè)置為true,解析屬性占位符失敗的時(shí)候(并且沒有為占位符配置默認(rèn)值)不會拋出異常,返回屬性原樣字符串,否則會拋出IllegalArgumentException。我們這里只需要分析AbstractPropertyResolver#resolveRequiredPlaceholders:
//AbstractPropertyResolver中的屬性: //ignoreUnresolvableNestedPlaceholders=true情況下創(chuàng)建的PropertyPlaceholderHelper實(shí)例 @Nullable private PropertyPlaceholderHelper nonStrictHelper; //ignoreUnresolvableNestedPlaceholders=false情況下創(chuàng)建的PropertyPlaceholderHelper實(shí)例 @Nullable private PropertyPlaceholderHelper strictHelper; //是否忽略無法處理的屬性占位符,這里是false,也就是遇到無法處理的屬性占位符且沒有默認(rèn)值則拋出異常 private boolean ignoreUnresolvableNestedPlaceholders = false; //屬性占位符前綴,這里是"${" private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX; //屬性占位符后綴,這里是"}" private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX; //屬性占位符解析失敗的時(shí)候配置默認(rèn)值的分隔符,這里是":" @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); } //創(chuàng)建一個(gè)新的PropertyPlaceholderHelper實(shí)例,這里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,這里需要重點(diǎn)注意:
注意到這里的第一個(gè)參數(shù)text就是屬性值的源字符串,例如我們需要處理的屬性為myProperties: ${server.port}-${spring.application.name},這里的text就是${server.port}-${spring.application.name}。
replacePlaceholders方法的第二個(gè)參數(shù)placeholderResolver,這里比較巧妙,這里的方法引用this::getPropertyAsRawString相當(dāng)于下面的代碼:
//PlaceholderResolver是一個(gè)函數(shù)式接口 @FunctionalInterface public interface PlaceholderResolver { @Nullable String resolvePlaceholder(String placeholderName); } //this::getPropertyAsRawString相當(dāng)于下面的代碼 return new PlaceholderResolver(){ @Override String resolvePlaceholder(String placeholderName){ //這里調(diào)用到的是PropertySourcesPropertyResolver#getPropertyAsRawString,有點(diǎn)繞 return getPropertyAsRawString(placeholderName); } }
接著看PropertyPlaceholderHelper#replacePlaceholders的源碼:
//基礎(chǔ)屬性 //占位符前綴,默認(rèn)是"${" private final String placeholderPrefix; //占位符后綴,默認(rèn)是"}" private final String placeholderSuffix; //簡單的占位符前綴,默認(rèn)是"{",主要用于處理嵌套的占位符如${xxxxx.{yyyyy}} private final String simplePrefix; //默認(rèn)值分隔符號,默認(rèn)是":" @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) { //搜索第一個(gè)占位符后綴的索引 int endIndex = findPlaceholderEndIndex(result, startIndex); if (endIndex != -1) { //提取第一個(gè)占位符中的原始字符串,如${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. // 遞歸調(diào)用,實(shí)際上就是解析嵌套的占位符,因?yàn)樘崛〉脑甲址锌赡苓€有一層或者多層占位符 placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); // Now obtain the value for the fully resolved key... // 遞歸調(diào)用完畢后,可以確定得到的字符串一定是不帶占位符,這個(gè)時(shí)候調(diào)用getPropertyAsRawString獲取key對應(yīng)的字符串值 String propVal = placeholderResolver.resolvePlaceholder(placeholder); // 如果字符串值為null,則進(jìn)行默認(rèn)值的解析,因?yàn)槟J(rèn)值有可能也使用了占位符,如${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); // 提取默認(rèn)值的字符串 String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length()); // 這里是把默認(rèn)值的表達(dá)式做一次解析,解析到null,則直接賦值為defaultValue propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder); if (propVal == null) { propVal = defaultValue; } } } // 上一步解析出來的值不為null,但是它有可能是一個(gè)帶占位符的值,所以后面對值進(jìn)行遞歸解析 if (propVal != null) { // Recursive invocation, parsing placeholders contained in the // previously resolved placeholder value. propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders); // 這一步很重要,替換掉第一個(gè)被解析完畢的占位符屬性,例如${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為下一個(gè)需要解析的占位符前綴的索引,可能為-1,說明解析結(jié)束 startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length()); } else if (this.ignoreUnresolvablePlaceholders) { // 如果propVal為null并且ignoreUnresolvablePlaceholders設(shè)置為true,直接返回當(dāng)前的占位符之間的原始字符串尾的索引,也就是跳過解析 // Proceed with unprocessed value. startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length()); } else { // 如果propVal為null并且ignoreUnresolvablePlaceholders設(shè)置為false,拋出異常 throw new IllegalArgumentException("Could not resolve placeholder '" + placeholder + "'" + " in value \"" + value + "\""); } // 遞歸結(jié)束移除判重集合中的元素 visitedPlaceholders.remove(originalPlaceholder); } else { // endIndex = -1說明解析結(jié)束 startIndex = -1; } } return result.toString(); } //基于傳入的起始索引,搜索第一個(gè)占位符后綴的索引,兼容嵌套的占位符 private int findPlaceholderEndIndex(CharSequence buf, int startIndex) { //這里index實(shí)際上就是實(shí)際需要解析的屬性的第一個(gè)字符,如${server.port},這里index指向s int index = startIndex + this.placeholderPrefix.length(); int withinNestedPlaceholder = 0; while (index < buf.length()) { //index指向"}",說明有可能到達(dá)占位符尾部或者嵌套占位符尾部 if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) { //存在嵌套占位符,則返回字符串中占位符后綴的索引值 if (withinNestedPlaceholder > 0) { withinNestedPlaceholder--; index = index + this.placeholderSuffix.length(); } else { //不存在嵌套占位符,直接返回占位符尾部索引 return index; } } //index指向"{",記錄嵌套占位符個(gè)數(shù)withinNestedPlaceholder加1,index更新為嵌套屬性的第一個(gè)字符的索引 else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) { withinNestedPlaceholder++; index = index + this.simplePrefix.length(); } else { //index不是"{"或者"}",則進(jìn)行自增 index++; } } //這里說明解析索引已經(jīng)超出了原字符串 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; }
上面的過程相對比較復(fù)雜,因?yàn)橛玫搅诉f歸,我們舉個(gè)實(shí)際的例子說明一下整個(gè)解析過程,例如我們使用了四個(gè)屬性項(xiàng),我們的目標(biāo)是獲取server.desc的值:
application.name=spring server.port=9090 spring.application.name=${application.name} server.desc=${server.port-${spring.application.name}}:${description:"hello"}
屬性類型轉(zhuǎn)換
在上一步解析屬性占位符完畢之后,得到的是屬性字符串值,可以把字符串轉(zhuǎn)換為指定的類型,此功能由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); }
實(shí)際上轉(zhuǎn)換的邏輯是委托到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實(shí)例,其實(shí)這一步相對復(fù)雜,匹配兩個(gè)類型的時(shí)候,會解析整個(gè)類的層次進(jìn)行對比 GenericConverter converter = getConverter(sourceType, targetType); if (converter != null) { // 實(shí)際上就是調(diào)用轉(zhuǎn)換方法 Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType); // 斷言最終結(jié)果和指定類型是否匹配并且返回 return handleResult(sourceType, targetType, result); } return handleConverterNotFound(source, sourceType, targetType); }
上面所有的可用的GenericConverter的實(shí)例可以在DefaultConversionService的addDefaultConverters中看到,默認(rèn)添加的轉(zhuǎn)換器實(shí)例已經(jīng)超過20個(gè),有些情況下如果無法滿足需求可以添加自定義的轉(zhuǎn)換器,實(shí)現(xiàn)GenericConverter接口添加進(jìn)去即可。
小結(jié)
SpringBoot在抽象整個(gè)類型轉(zhuǎn)換器方面做的比較好,在SpringMVC應(yīng)用中,采用的是org.springframework.boot.autoconfigure.web.format.WebConversionService,兼容了Converter、Formatter、ConversionService等轉(zhuǎn)換器類型并且對外提供一套統(tǒng)一的轉(zhuǎn)換方法。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。