具有了技術深度,遇到問題能夠快速定位並從根本上解決。有了技術深度以後,學習其它技術能夠更快,再深刻其它技術也就不會懼怕java
【小家Spring】聊聊Spring中的數據轉換:Converter、ConversionService、TypeConverter、PropertyEditor面試
【小家Spring】聊聊Spring中的數據綁定 --- 屬性訪問器PropertyAccessor和實現類DirectFieldAccessor的使用【小家Spring】聊聊Spring中的數據綁定 --- BeanWrapper以及Java內省Introspector和PropertyDescriptor架構
Java高工、架構師3羣
(文末有二維碼)
書寫此篇博文的原因是出自一道面試題:面試題目大概如標題所述。我我的認爲這道面試題問得是很是有水平的,由於它涉及到的知識點既有深度,又有廣度,可謂一箭雙鵰~~~所以在這裏分享給你們。app
爲了給此文作鋪墊,前面已經有兩篇文章分別敘述了Java內省和BeanWrapper
,並且還分析了底層接口:屬性訪問器(PropertyAccessor
)。若對此部分還不是很瞭解的話,建議能夠先出門左拐或者單擊【相關閱讀】裏的連接~框架
Spring須要依賴注入就須要使用BeanWrapper
,上章節說了BeanWrapperImpl
的實現大都委託給了CachedIntrospectionResults
去完成,而CachedIntrospectionResults
它的核心說法就是Java內省機制。函數
從層層委託的依賴關係能夠看出,Spring IoC
的依賴注入(給屬性賦值)是層層委託的最終給了Java內省機制,這是Spring框架設計精妙處之一。這也符合我上文所訴:BeanWrapper
這個接口並不建議應用本身去直接使用~~~那麼本文就着眼於此,結合源碼去分析Spring IoC容器它使用BeanWrapper
完成屬性賦值(依賴注入)之精華~源碼分析
BeanWrapper
源碼分析Spring IoC
我相信小夥伴並不陌生了,但IoC
的細節不是本文的重點。爲了便於分析,我把這個過程畫一個時序圖描述以下:有了這個簡略的時序圖,接下來就一步一步的分析吧post
任何建立Bean的過程,都得經歷doCreateBean()
。這句代碼咱們已經很是熟悉了,它在AbstractAutowireCapableBeanFactory
裏:學習
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {
BeanWrapper instanceWrapper = null;
...
// 這一步簡單的說:經過構造函數實例化Bean後,new BeanWrapperImpl(beanInstance)包裝起來
// 而且:initBeanWrapper(bw); 做用是註冊ConversionService和registerCustomEditors() ...
instanceWrapper = createBeanInstance(beanName, mbd, args);
...
// 給屬性賦值:此處會實施BeanWrapper的真正實力~~~~
// 注意:此處第三個參數傳入的是BeanWrapper,而不是源生beanduixiang~~~
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
...
}複製代碼
doCreateBean
這個方法完成整個Bean的實例化、初始化。而這裏面咱們最爲關注的天然就是populateBean()
這個方法,它的做用是完成給屬性賦值,從時序圖中也能夠看出這是一個入口ui
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
...
// 從Bean定義裏面把準備好的值都拿出來~~~
// 它是個MutablePropertyValues,持有N多個屬性的值~~~
PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
...
for (BeanPostProcessor bp : getBeanPostProcessors()) {
...
// 此處會從後置處理,從裏面把依賴的屬性,值都拿到。好比大名鼎鼎的AutowiredAnnotationBeanPostProcessor就是在此處拿出值的~~~
PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
...
pvs = pvsToUse;
}
...
// 若存在屬性pvs ,那就作賦值操做吧~~~(本處纔是今天關心的重點~~~)
if (pvs != null) {
applyPropertyValues(beanName, mbd, bw, pvs);
}
}複製代碼
深刻到方法內部,它完成了k-v值的準備工做,不少重要的BeanPostProcessor
也在此處獲得執行。對於最終給屬性賦值的步驟,是交給了本類的applyPropertyValues()
方法去完成~~~
其實到了此處,理論上小夥伴就應該就能猜到接下來的核心下文了~
applyPropertyValues()
:完成屬性賦值這個方法的處理內容纔是本文最應該關注的核心,它在處理數據解析、轉換這一塊仍是存在不小的複雜度的~
// 本方法傳入了beanName和bean定義信息,以及它對應的BeanWrapper和value值們~
protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
if (pvs.isEmpty()) {
return;
}
...
MutablePropertyValues mpvs = null;
List<PropertyValue> original;
// 說明一下:爲什麼這裏仍是要判斷一下,雖然Spring對PropertyValues的內建實現只有MutablePropertyValues
// 可是這個是調用者本身也能夠實現邏輯的~~~so判斷一下最佳~~~~
if (pvs instanceof MutablePropertyValues) {
mpvs = (MutablePropertyValues) pvs;
// 此處有個短路處理:
// 若該mpvs中的全部屬性值都已經轉換爲對應的類型,則把mpvs設置到BeanWrapper中,返回
if (mpvs.isConverted()) {
// Shortcut: use the pre-converted values as-is.
try {
bw.setPropertyValues(mpvs);
return;
} catch (BeansException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Error setting property values", ex);
}
}
// 不然,拿到裏面的屬性值們~~~
original = mpvs.getPropertyValueList();
} else {
original = Arrays.asList(pvs.getPropertyValues());
}
// 顯然,若調用者沒有自定義轉換器,那就使用BeanWrapper自己~~~(由於BeanWrapper實現了TypeConverter 接口~~)
TypeConverter converter = getCustomTypeConverter();
if (converter == null) {
converter = bw;
}
// 獲取BeanDefinitionValueResolver,該Bean用於將bean定義對象中包含的值解析爲應用於目標bean實例的實際值。
BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);
// Create a deep copy, resolving any references for values.
// 此處翻譯成深度拷貝不合適,倒不如翻譯成深度解析更爲合理~~~~
List<PropertyValue> deepCopy = new ArrayList<>(original.size());
boolean resolveNecessary = false;
// 遍歷沒有被解析的original屬性值們~~~~
for (PropertyValue pv : original) {
if (pv.isConverted()) {
deepCopy.add(pv);
} else { // 那種還沒被解析過的PropertyValue此處會一步步解析~~~~
String propertyName = pv.getName(); // 屬性名稱
Object originalValue = pv.getValue(); // 未經類型轉換的值(注意:是未經轉換的,可能還只是個字符串或者表達式而已~~~~)
// 最爲複雜的解析邏輯~~~
Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
Object convertedValue = resolvedValue;
// 屬性可寫 而且 不是嵌套(如foo.bar,java中用getFoo().getBar()表示)或者索引(如person.addresses[0])屬性
boolean convertible = bw.isWritableProperty(propertyName) && !PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
if (convertible) {
// 用類型轉換器進行轉換
convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
}
if (resolvedValue == originalValue) {
if (convertible) {
pv.setConvertedValue(convertedValue);
}
deepCopy.add(pv);
} else if (convertible && originalValue instanceof TypedStringValue &&
!((TypedStringValue) originalValue).isDynamic() &&
!(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) {
pv.setConvertedValue(convertedValue);
deepCopy.add(pv);
}
else {
resolveNecessary = true;
deepCopy.add(new PropertyValue(pv, convertedValue));
}
}
}
// 標記mpvs已經轉換
if (mpvs != null && !resolveNecessary) {
mpvs.setConverted();
}
// Set our (possibly massaged) deep copy.
// 使用轉換後的值進行填充~~~~~~~~~~
try {
bw.setPropertyValues(new MutablePropertyValues(deepCopy));
} catch (BeansException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Error setting property values", ex);
}
}
// 屬性值的轉換
@Nullable
private Object convertForProperty(@Nullable Object value, String propertyName, BeanWrapper bw, TypeConverter converter) {
// 須要特別注意的是:convertForProperty方法是BeanWrapperImpl的實例方法,並不是接口方法
// 這個方法內部就用到了CachedIntrospectionResults,從何就和Java內省搭上了關係~~~
if (converter instanceof BeanWrapperImpl) {
return ((BeanWrapperImpl) converter).convertForProperty(value, propertyName);
} else { // 自定義轉換器
PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd);
return converter.convertIfNecessary(value, pd.getPropertyType(), methodParam);
}
}複製代碼
說明:BeanDefinitionValueResolver
是Spring一個內建的非public
類,它在上述步驟中承擔了很是多的任務,具體可參考此處:BeanDefinitionValueResolver和PropertyValues
從命名中就能看出,它處理BeanDefinition
的各式各樣的狀況,它主要是在xml
配置時代起到了很是大的做用,形如這樣:
<bean class="foo.bar.xxx">
<property name="referBeanName" ref="otherBeanName" />
</bean>複製代碼
由於咱們知道在xml
時代配置Bean很是的靈活:引用Bean、Map、List甚至支持SpEL等等,這一切權得益於BeanDefinitionValueResolver
這個類來處理各類case~
其實在如今註解大行其道的今天,配置Bean咱們大都使用@Bean
來配置,它是一種工廠方法的實現,所以這個處理類的做用就被弱化了不少。可是,可是,可是,它仍舊是咱們實施定製化BeanDefinition的一個有力武器~
applyPropertyValues()
這一步完成以後,就完全完成了對Bean實例屬性的賦值。從中能夠看到最終的賦值操做,核心依賴的就是這麼一句話:
bw.setPropertyValues(new MutablePropertyValues(deepCopy))複製代碼
而且從轉換的邏輯咱們也須要知道的是:IoC並非100%得使用BeanWrapper
的,若咱們是自定義了一個轉換器,實際上是能夠不通過Java內省機制,而是直接經過反射來實現的,固然並不建議這麼去作~
BeanWrapper
體系相比於 Spring 中其餘體系是比較簡單的,它做爲BeanDefinition
向 Bean
轉換過程當中的中間產物,承載了 bean 實例的包裝、類型轉換、屬性的設置以及訪問等重要做用(請不要落了訪問這個重要能力)。
關於此面試題怎麼去回答,若是是我主考我會這麼評價回答:
populateBean()
這裏算是對這塊知識入門了 applyPropertyValues()
這裏,那基本對此回答就比較滿意了 BeanDefinition
這塊(雖然與本問題沒有必然關聯),也是能夠加分的(畢竟是面試而非考試~)
若文章格式混亂,可點擊
:原文連接-原文連接-原文連接-原文連接-原文連接
==The last:若是以爲本文對你有幫助,不妨點個讚唄。固然分享到你的朋友圈讓更多小夥伴看到也是被做者本人許可的~
==
**若對技術內容感興趣能夠加入wx羣交流:`Java高工、架構師3羣`。若羣二維碼失效,請加wx號:fsx641385712
(或者掃描下方wx二維碼)。而且備註:"java入羣"
字樣,會手動邀請入羣**