一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|JavaScript|易語言|

服務器之家 - 編程語言 - Java教程 - Spring Boot環境屬性占位符解析及類型轉換詳解

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

2021-05-24 13:25throwable Java教程

這篇文章主要給大家介紹了關于Spring Boot環境屬性占位符解析及類型轉換的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

前提

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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:

?
1
2
3
4
protected string resolvenestedplaceholders(string value) {
 return (this.ignoreunresolvablenestedplaceholders ?
  resolveplaceholders(value) : resolverequiredplaceholders(value));
}

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//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相當于下面的代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//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的源碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
//基礎屬性
//占位符前綴,默認是"${"
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的值:

?
1
2
3
4
application.name=spring
server.port=9090
spring.application.name=${application.name}
server.desc=${server.port-${spring.application.name}}:${description:"hello"}

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

屬性類型轉換

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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等轉換器類型并且對外提供一套統一的轉換方法。

總結

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

原文鏈接:https://www.cnblogs.com/throwable/p/9417827.html

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 操熟美女又肥又嫩的骚屁股 | 国产精品一级视频 | 美女班主任下面好爽好湿好紧 | 岛国片免费看 | ts人妖另类国产 | 黑白配高清hd在线视频 | 美女口述又粗又大感觉 | 国产麻豆流白浆在线观看 | 美女口述又粗又大感觉 | 国产综合欧美日韩视频一区 | 国产午夜精品久久久久 | 日本免费一区二区三区四区五六区 | 丁香六月婷婷激情 | 日本三级免费看 | 欧美精品一区二区三区免费观看 | 国产高清国内精品福利色噜噜 | 国产欧美精品一区二区三区四区 | 亚洲国产区中文在线观看 | 免费日批软件 | 国产精品理论片在线观看 | 国产精品视频二区不卡 | 国产亚洲人成网站在线观看不卡 | 好男人在线观看hd中字 | 51xtv成人影院| 国产综合社区 | 91精品国产91久久久久久麻豆 | 亚洲小视频在线 | 亚洲国产精品一区二区首页 | 日本丰满www色 | 黑人粗长巨茎小说 | 婚色阿花在线全文免费笔 | 男女天堂 | 国产精品探花一区在线观看 | 国产主播精品在线 | 欧美另类bbbxxxxx另类 | 免费国产好深啊好涨好硬视频 | 成人影院www在线观看 | 天天爽天天操 | 免费国产午夜高清在线视频 | 桥本有菜在线四虎福利网 | 动漫jk美女被爆羞羞漫画 |