摘要:從層層委托的依賴關(guān)系可以看出,的依賴注入給屬性賦值是層層委托的最終給了內(nèi)省機(jī)制,這是框架設(shè)計(jì)精妙處之一。當(dāng)然分享到你的朋友圈讓更多小伙伴看到也是被作者本人許可的若對技術(shù)內(nèi)容感興趣可以加入群交流高工架構(gòu)師群。
每篇一句
具備了技術(shù)深度,遇到問題可以快速定位并從根本上解決。有了技術(shù)深度之后,學(xué)習(xí)其它技術(shù)可以更快,再深入其它技術(shù)也就不會害怕相關(guān)閱讀
【小家Spring】聊聊Spring中的數(shù)據(jù)轉(zhuǎn)換:Converter、ConversionService、TypeConverter、PropertyEditor
【小家Spring】聊聊Spring中的數(shù)據(jù)綁定 --- 屬性訪問器PropertyAccessor和實(shí)現(xiàn)類DirectFieldAccessor的使用
【小家Spring】聊聊Spring中的數(shù)據(jù)綁定 --- BeanWrapper以及Java內(nèi)省Introspector和PropertyDescriptor
書寫此篇博文的緣由是出自一道面試題:面試題目大概如標(biāo)題所述。
我個人認(rèn)為這道面試題問得是非常有水平的,因?yàn)樗婕暗降闹R點(diǎn)既有深度,又有廣度,可謂一舉兩得~~~因此在這里分享給大家。
為了給此文做鋪墊,前面已經(jīng)有兩篇文章分別敘述了Java內(nèi)省和BeanWrapper,而且還分析了底層接口:屬性訪問器(PropertyAccessor)。若對此部分還不是很了解的話,建議可以先出門左拐或者單擊【相關(guān)閱讀】里的鏈接~
Spring IoC和Java內(nèi)省的依賴關(guān)系說明Spring需要依賴注入就需要使用BeanWrapper,上章節(jié)說了BeanWrapperImpl的實(shí)現(xiàn)大都委托給了CachedIntrospectionResults去完成,而CachedIntrospectionResults它的核心說法就是Java內(nèi)省機(jī)制。
從層層委托的依賴關(guān)系可以看出,Spring IoC的依賴注入(給屬性賦值)是層層委托的最終給了Java內(nèi)省機(jī)制,這是Spring框架設(shè)計(jì)精妙處之一。這也符合我上文所訴:BeanWrapper這個接口并不建議應(yīng)用自己去直接使用~~~
那么本文就著眼于此,結(jié)合源碼去分析Spring IoC容器它使用BeanWrapper完成屬性賦值(依賴注入)之精華~
Spring IoC我相信小伙伴并不陌生了,但IoC的細(xì)節(jié)不是本文的重點(diǎn)。為了便于分析,我把這個過程畫一個時序圖描述如下:
有了這個簡略的時序圖,接下來就一步一步的分析吧
任何創(chuàng)建Bean的過程,都得經(jīng)歷doCreateBean()。這句代碼我們已經(jīng)非常熟悉了,它在AbstractAutowireCapableBeanFactory里:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper = null; ... // 這一步簡單的說:通過構(gòu)造函數(shù)實(shí)例化Bean后,new BeanWrapperImpl(beanInstance)包裝起來 // 并且:initBeanWrapper(bw); 作用是注冊ConversionService和registerCustomEditors() ... instanceWrapper = createBeanInstance(beanName, mbd, args); ... // 給屬性賦值:此處會實(shí)施BeanWrapper的真正實(shí)力~~~~ // 注意:此處第三個參數(shù)傳入的是BeanWrapper,而不是源生beanduixiang~~~ populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd); ... }
doCreateBean這個方法完成整個Bean的實(shí)例化、初始化。而這里面我們最為關(guān)注的自然就是populateBean()這個方法,它的作用是完成給屬性賦值,從時序圖中也可以看出這是一個入口
populateBean():給Bean的屬性賦值~protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { ... // 從Bean定義里面把準(zhǔn)備好的值都拿出來~~~ // 它是個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 ,那就做賦值操作吧~~~(本處才是今天關(guān)心的重點(diǎn)~~~) if (pvs != null) { applyPropertyValues(beanName, mbd, bw, pvs); } }
深入到方法內(nèi)部,它完成了k-v值的準(zhǔn)備工作,很多重要的BeanPostProcessor 也在此處得到執(zhí)行。對于最終給屬性賦值的步驟,是交給了本類的applyPropertyValues()方法去完成~~~
其實(shí)到了此處,理論上小伙伴就應(yīng)該就能猜到接下來的核心下文了~applyPropertyValues():完成屬性賦值
這個方法的處理內(nèi)容才是本文最應(yīng)該關(guān)注的核心,它在處理數(shù)據(jù)解析、轉(zhuǎn)換這一塊還是存在不小的復(fù)雜度的~
// 本方法傳入了beanName和bean定義信息,以及它對應(yīng)的BeanWrapper和value值們~ protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) { if (pvs.isEmpty()) { return; } ... MutablePropertyValues mpvs = null; Listoriginal; // 說明一下:為何這里還是要判斷一下,雖然Spring對PropertyValues的內(nèi)建實(shí)現(xiàn)只有MutablePropertyValues // 但是這個是調(diào)用者自己也可以實(shí)現(xiàn)邏輯的~~~so判斷一下最佳~~~~ if (pvs instanceof MutablePropertyValues) { mpvs = (MutablePropertyValues) pvs; // 此處有個短路處理: // 若該mpvs中的所有屬性值都已經(jīng)轉(zhuǎn)換為對應(yīng)的類型,則把mpvs設(shè)置到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()); } // 顯然,若調(diào)用者沒有自定義轉(zhuǎn)換器,那就使用BeanWrapper本身~~~(因?yàn)锽eanWrapper實(shí)現(xiàn)了TypeConverter 接口~~) TypeConverter converter = getCustomTypeConverter(); if (converter == null) { converter = bw; } // 獲取BeanDefinitionValueResolver,該Bean用于將bean定義對象中包含的值解析為應(yīng)用于目標(biāo)bean實(shí)例的實(shí)際值。 BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter); // Create a deep copy, resolving any references for values. // 此處翻譯成深度拷貝不合適,倒不如翻譯成深度解析更為合理~~~~ List 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(); // 未經(jīng)類型轉(zhuǎn)換的值(注意:是未經(jīng)轉(zhuǎn)換的,可能還只是個字符串或者表達(dá)式而已~~~~) // 最為復(fù)雜的解析邏輯~~~ 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) { // 用類型轉(zhuǎn)換器進(jìn)行轉(zhuǎn)換 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)); } } } // 標(biāo)記mpvs已經(jīng)轉(zhuǎn)換 if (mpvs != null && !resolveNecessary) { mpvs.setConverted(); } // Set our (possibly massaged) deep copy. // 使用轉(zhuǎn)換后的值進(jìn)行填充~~~~~~~~~~ try { bw.setPropertyValues(new MutablePropertyValues(deepCopy)); } catch (BeansException ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Error setting property values", ex); } } // 屬性值的轉(zhuǎn)換 @Nullable private Object convertForProperty(@Nullable Object value, String propertyName, BeanWrapper bw, TypeConverter converter) { // 需要特別注意的是:convertForProperty方法是BeanWrapperImpl的實(shí)例方法,并非接口方法 // 這個方法內(nèi)部就用到了CachedIntrospectionResults,從何就和Java內(nèi)省搭上了關(guān)系~~~ if (converter instanceof BeanWrapperImpl) { return ((BeanWrapperImpl) converter).convertForProperty(value, propertyName); } else { // 自定義轉(zhuǎn)換器 PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName); MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd); return converter.convertIfNecessary(value, pd.getPropertyType(), methodParam); } }
說明:BeanDefinitionValueResolver是Spring一個內(nèi)建的非public類,它在上述步驟中承擔(dān)了非常多的任務(wù),具體可參考此處:BeanDefinitionValueResolver和PropertyValues
從命名中就能看出,它處理BeanDefinition的各式各樣的情況,它主要是在xml配置時代起到了非常大的作用,形如這樣:
因?yàn)槲覀冎涝?b>xml時代配置Bean非常的靈活:引用Bean、Map、List甚至支持SpEL等等,這一切權(quán)得益于BeanDefinitionValueResolver這個類來處理各種case~
其實(shí)在現(xiàn)在注解大行其道的今天,配置Bean我們大都使用@Bean來配置,它是一種工廠方法的實(shí)現(xiàn),因此這個處理類的作用就被弱化了很多。但是,但是,但是,它仍舊是我們實(shí)施定制化BeanDefinition的一個有力武器~
applyPropertyValues()這一步完成之后,就徹底完成了對Bean實(shí)例屬性的賦值。從中可以看到最終的賦值操作,核心依賴的就是這么一句話:
bw.setPropertyValues(new MutablePropertyValues(deepCopy))
并且從轉(zhuǎn)換的邏輯我們也需要知道的是:IoC并不是100%得使用BeanWrapper的,若我們是自定義了一個轉(zhuǎn)換器,其實(shí)是可以不經(jīng)過Java內(nèi)省機(jī)制,而是直接通過反射來實(shí)現(xiàn)的,當(dāng)然并不建議這么去做~
總結(jié)BeanWrapper體系相比于 Spring 中其他體系是比較簡單的,它作為BeanDefinition向 Bean轉(zhuǎn)換過程中的中間產(chǎn)物,承載了 bean 實(shí)例的包裝、類型轉(zhuǎn)換、屬性的設(shè)置以及訪問等重要作用(請不要落了訪問這個重要能力)。
關(guān)于此面試題怎么去回答,如果是我主考我會這么評價回答:
能答到populateBean()這里算是對這塊知識入門了
能答到applyPropertyValues()這里,那基本對此回答就比較滿意了
當(dāng)然若能答到:通過自定義實(shí)現(xiàn)一個轉(zhuǎn)換器+反射實(shí)現(xiàn)作為實(shí)現(xiàn),而繞過Java內(nèi)省機(jī)制。那勢必就可以加分了~
1. 若達(dá)到自定義、個性化定義`BeanDefinition`這塊(雖然與本問題沒有必然關(guān)聯(lián)),也是可以加分的(畢竟是面試而非考試~)知識交流
若文章格式混亂,可點(diǎn)擊:原文鏈接-原文鏈接-原文鏈接-原文鏈接-原文鏈接
==The last:如果覺得本文對你有幫助,不妨點(diǎn)個贊唄。當(dāng)然分享到你的朋友圈讓更多小伙伴看到也是被作者本人許可的~==
**若對技術(shù)內(nèi)容感興趣可以加入wx群交流:Java高工、架構(gòu)師3群。
若群二維碼失效,請加wx號:fsx641385712(或者掃描下方wx二維碼)。并且備注:"java入群" 字樣,會手動邀請入群**
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/75388.html
摘要:關(guān)于它的數(shù)據(jù)轉(zhuǎn)換使用了如下兩種機(jī)制隸屬于規(guī)范。這種類中的方法主要用于訪問私有的字段,且方法名符合某種命名規(guī)則。如果在兩個模塊之間傳遞信息,可以將信息封裝進(jìn)中,這種對象稱為值對象,或。 每篇一句 千古以來要飯的沒有要早飯的,知道為什么嗎? 相關(guān)閱讀 【小家Spring】聊聊Spring中的數(shù)據(jù)轉(zhuǎn)換:Converter、ConversionService、TypeConverter、Pro...
摘要:對中的數(shù)據(jù)綁定場景,小伙伴們就再熟悉不過了。比如包下大名鼎鼎的源碼分析的源碼相對來說還是頗為復(fù)雜的,它提供的能力非常強(qiáng)大,也注定了它的方法非常多屬性也非常多。并且備注入群字樣,會手動邀請入群 每篇一句 唯有熱愛和堅(jiān)持,才能讓你在程序人生中屹立不倒,切忌跟風(fēng)什么語言或就學(xué)什么去~ 相關(guān)閱讀 【小家Spring】聊聊Spring中的數(shù)據(jù)綁定 --- 屬性訪問器PropertyAccesso...
摘要:關(guān)于使用這種方式我還有必要再說明一點(diǎn)若自己設(shè)置了加載屬性文件,這句代碼對此種場景就沒有必要了,配置的占位符也是能夠讀取到的。 每篇一句 大師都是偏執(zhí)的,偏執(zhí)才能產(chǎn)生力量,妥協(xié)是沒有力量的。你對全世界妥協(xié)了你就是空氣。所以若沒有偏見,哪來的大師呢 相關(guān)閱讀 【小家Spring】詳解PropertyPlaceholderConfigurer、PropertyOverrideConfigur...
摘要:每篇一句不要總問低級的問題,這樣的人要么懶,不愿意上網(wǎng)搜索,要么笨,一點(diǎn)獨(dú)立思考的能力都沒有相關(guān)閱讀小家聊聊中的數(shù)據(jù)綁定本尊源碼分析小家聊聊中的數(shù)據(jù)綁定屬性訪問器和實(shí)現(xiàn)類的使用小家聊聊中的數(shù)據(jù)綁定以及內(nèi)省和對感興趣可掃碼加 每篇一句 不要總問低級的問題,這樣的人要么懶,不愿意上網(wǎng)搜索,要么笨,一點(diǎn)獨(dú)立思考的能力都沒有 相關(guān)閱讀 【小家Spring】聊聊Spring中的數(shù)據(jù)綁定 --- ...
摘要:你也會了解到構(gòu)造對象的兩種策略。構(gòu)造方法參數(shù)數(shù)量低于配置的參數(shù)數(shù)量,則忽略當(dāng)前構(gòu)造方法,并重試。通過默認(rèn)構(gòu)造方法創(chuàng)建對象看完了上面冗長的邏輯,本節(jié)來看點(diǎn)輕松的吧通過默認(rèn)構(gòu)造方法創(chuàng)建對象。 1. 簡介 本篇文章是上一篇文章(創(chuàng)建單例 bean 的過程)的延續(xù)。在上一篇文章中,我們從戰(zhàn)略層面上領(lǐng)略了doCreateBean方法的全過程。本篇文章,我們就從戰(zhàn)術(shù)的層面上,詳細(xì)分析doCreat...
閱讀 1384·2021-11-25 09:43
閱讀 3604·2021-11-10 11:48
閱讀 5175·2021-09-23 11:21
閱讀 1608·2019-08-30 15:55
閱讀 3519·2019-08-30 13:53
閱讀 1245·2019-08-30 10:51
閱讀 880·2019-08-29 14:20
閱讀 1985·2019-08-29 13:11