最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot環(huán)境屬性占位符解析和類型轉(zhuǎn)換方式

 更新時(shí)間:2023年11月27日 11:12:18   作者:Throwable文摘  
這篇文章主要介紹了SpringBoot環(huán)境屬性占位符解析和類型轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

前提

前面寫過一篇關(guān)于Environment屬性加載的源碼分析和擴(kuò)展,里面提到屬性的占位符解析和類型轉(zhuǎn)換是相對(duì)復(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)值)不會(huì)拋出異常,返回屬性原樣字符串,否則會(huì)拋出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−server.port− {spring.application.name},這里的text就是 server\.port−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)值分隔符號(hào),默認(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對(duì)應(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è)帶占位符的值,所以后面對(duì)值進(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,此方法會(huì)檢查原始字符串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;
}

上面的過程相對(duì)比較復(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...
        // 這里一般只有字符串類型才會(huì)命中
        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í)這一步相對(duì)復(fù)雜,匹配兩個(gè)類型的時(shí)候,會(huì)解析整個(gè)類的層次進(jìn)行對(duì)比
    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)換器類型并且對(duì)外提供一套統(tǒng)一的轉(zhuǎn)換方法。

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Springboot集合前端實(shí)現(xiàn)進(jìn)度條顯示功能實(shí)例

    Springboot集合前端實(shí)現(xiàn)進(jìn)度條顯示功能實(shí)例

    這篇文章主要介紹了使用進(jìn)度條提升用戶體驗(yàn)的原因,特別是在處理大文件上傳、下載或長時(shí)間運(yùn)行的操作時(shí),進(jìn)度條通過實(shí)時(shí)反饋任務(wù)進(jìn)度,減少用戶的不確定感,文中給出了詳細(xì)的代碼示例,需要的朋友可以參考下
    2024-11-11
  • 分享Spring的下載組件

    分享Spring的下載組件

    這篇文章主要為大家分享了Spring的下載組件,感興趣的小伙伴們可以參考一下
    2016-05-05
  • 詳解Spring配置文件中bean的相關(guān)屬性

    詳解Spring配置文件中bean的相關(guān)屬性

    這篇文章主要為大家詳細(xì)介紹了Spring配置文件中bean的相關(guān)屬性的知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-12-12
  • 用Java實(shí)現(xiàn)24點(diǎn)游戲

    用Java實(shí)現(xiàn)24點(diǎn)游戲

    喜歡玩游戲的有福啦,文中有非常詳細(xì)的開發(fā)框架,按著框架來實(shí)現(xiàn)就好啦.而且24點(diǎn)游戲是經(jīng)典的紙牌益智游戲.,需要的朋友可以參考下
    2021-05-05
  • Java線程間協(xié)作wait、notify和notifyAll詳解

    Java線程間協(xié)作wait、notify和notifyAll詳解

    這篇文章主要介紹了Java線程間協(xié)作wait、notify和notifyAll詳解,在 Java 中可以用 wait、notify 和 notifyAll 來實(shí)現(xiàn)線程間的通信,盡管關(guān)于wait和notify的概念很基礎(chǔ),它們也都是Object類的函數(shù),但用它們來寫代碼卻并不簡單,,需要的朋友可以參考下
    2023-10-10
  • Java中的邏輯控制語句詳解

    Java中的邏輯控制語句詳解

    下面小編就為大家?guī)硪黄狫ava邏輯控制的基礎(chǔ)文章。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2021-08-08
  • SpringBoot2.x集成Dozer的示例代碼

    SpringBoot2.x集成Dozer的示例代碼

    本文主要介紹了SpringBoot2.x集成Dozer的示例代碼,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • Java Stream map, Collectors(toMap, toList, toSet, groupingBy, collectingAndThen)使用案例

    Java Stream map, Collectors(toMap, toLis

    這篇文章主要介紹了Java Stream map, Collectors(toMap, toList, toSet, groupingBy, collectingAndThen)使用案例,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-09-09
  • mybatis-plus主鍵生成策略

    mybatis-plus主鍵生成策略

    這篇文章主要介紹了mybatis-plus主鍵生成策略,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • JAVA中堆、棧,靜態(tài)方法和非靜態(tài)方法的速度問題

    JAVA中堆、棧,靜態(tài)方法和非靜態(tài)方法的速度問題

    這篇文章主要介紹了JAVA中堆、棧,靜態(tài)方法和非靜態(tài)方法的速度問題,堆和棧得速度性能分析多角度給大家分析,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-08-08

最新評(píng)論

恩平市| 疏附县| 香港 | 蓝山县| 万安县| 定日县| 东海县| 尼玛县| 内江市| 宁晋县| 蒲江县| 彭水| 太保市| 岑巩县| 溆浦县| 鄂州市| 墨脱县| 长丰县| 万州区| 札达县| 宁强县| 南通市| 新化县| 陵川县| 修武县| 巴中市| 千阳县| 汤阴县| 米易县| 太仆寺旗| 道孚县| 四平市| 盱眙县| 蓝田县| 荔浦县| 永兴县| 土默特左旗| 福泉市| 岱山县| 伽师县| 克什克腾旗|