摘要:從層層委托的依賴關系可以看出,的依賴注入給屬性賦值是層層委托的最終給了內省機制,這是框架設計精妙處之一。當然分享到你的朋友圈讓更多小伙伴看到也是被作者本人許可的若對技術內容感興趣可以加入群交流高工架構師群。
每篇一句
具備了技術深度,遇到問題可以快速定位并從根本上解決。有了技術深度之后,學習其它技術可以更快,再深入其它技術也就不會害怕相關閱讀
【小家Spring】聊聊Spring中的數據轉換:Converter、ConversionService、TypeConverter、PropertyEditor
【小家Spring】聊聊Spring中的數據綁定 --- 屬性訪問器PropertyAccessor和實現類DirectFieldAccessor的使用
【小家Spring】聊聊Spring中的數據綁定 --- BeanWrapper以及Java內省Introspector和PropertyDescriptor
書寫此篇博文的緣由是出自一道面試題:面試題目大概如標題所述。
我個人認為這道面試題問得是非常有水平的,因為它涉及到的知識點既有深度,又有廣度,可謂一舉兩得~~~因此在這里分享給大家。
為了給此文做鋪墊,前面已經有兩篇文章分別敘述了Java內省和BeanWrapper,而且還分析了底層接口:屬性訪問器(PropertyAccessor)。若對此部分還不是很了解的話,建議可以先出門左拐或者單擊【相關閱讀】里的鏈接~
Spring IoC和Java內省的依賴關系說明Spring需要依賴注入就需要使用BeanWrapper,上章節說了BeanWrapperImpl的實現大都委托給了CachedIntrospectionResults去完成,而CachedIntrospectionResults它的核心說法就是Java內省機制。
從層層委托的依賴關系可以看出,Spring IoC的依賴注入(給屬性賦值)是層層委托的最終給了Java內省機制,這是Spring框架設計精妙處之一。這也符合我上文所訴:BeanWrapper這個接口并不建議應用自己去直接使用~~~
那么本文就著眼于此,結合源碼去分析Spring IoC容器它使用BeanWrapper完成屬性賦值(依賴注入)之精華~
Spring IoC我相信小伙伴并不陌生了,但IoC的細節不是本文的重點。為了便于分析,我把這個過程畫一個時序圖描述如下:
有了這個簡略的時序圖,接下來就一步一步的分析吧
任何創建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()這個方法,它的作用是完成給屬性賦值,從時序圖中也可以看出這是一個入口
populateBean():給Bean的屬性賦值~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; Listoriginal; // 說明一下:為何這里還是要判斷一下,雖然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 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配置時代起到了非常大的作用,形如這樣:
因為我們知道在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()這里,那基本對此回答就比較滿意了
當然若能答到:通過自定義實現一個轉換器+反射實現作為實現,而繞過Java內省機制。那勢必就可以加分了~
1. 若達到自定義、個性化定義`BeanDefinition`這塊(雖然與本問題沒有必然關聯),也是可以加分的(畢竟是面試而非考試~)知識交流
若文章格式混亂,可點擊:原文鏈接-原文鏈接-原文鏈接-原文鏈接-原文鏈接
==The last:如果覺得本文對你有幫助,不妨點個贊唄。當然分享到你的朋友圈讓更多小伙伴看到也是被作者本人許可的~==
**若對技術內容感興趣可以加入wx群交流:Java高工、架構師3群。
若群二維碼失效,請加wx號:fsx641385712(或者掃描下方wx二維碼)。并且備注:"java入群" 字樣,會手動邀請入群**
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/75388.html
摘要:關于它的數據轉換使用了如下兩種機制隸屬于規范。這種類中的方法主要用于訪問私有的字段,且方法名符合某種命名規則。如果在兩個模塊之間傳遞信息,可以將信息封裝進中,這種對象稱為值對象,或。 每篇一句 千古以來要飯的沒有要早飯的,知道為什么嗎? 相關閱讀 【小家Spring】聊聊Spring中的數據轉換:Converter、ConversionService、TypeConverter、Pro...
摘要:對中的數據綁定場景,小伙伴們就再熟悉不過了。比如包下大名鼎鼎的源碼分析的源碼相對來說還是頗為復雜的,它提供的能力非常強大,也注定了它的方法非常多屬性也非常多。并且備注入群字樣,會手動邀請入群 每篇一句 唯有熱愛和堅持,才能讓你在程序人生中屹立不倒,切忌跟風什么語言或就學什么去~ 相關閱讀 【小家Spring】聊聊Spring中的數據綁定 --- 屬性訪問器PropertyAccesso...
摘要:關于使用這種方式我還有必要再說明一點若自己設置了加載屬性文件,這句代碼對此種場景就沒有必要了,配置的占位符也是能夠讀取到的。 每篇一句 大師都是偏執的,偏執才能產生力量,妥協是沒有力量的。你對全世界妥協了你就是空氣。所以若沒有偏見,哪來的大師呢 相關閱讀 【小家Spring】詳解PropertyPlaceholderConfigurer、PropertyOverrideConfigur...
摘要:每篇一句不要總問低級的問題,這樣的人要么懶,不愿意上網搜索,要么笨,一點獨立思考的能力都沒有相關閱讀小家聊聊中的數據綁定本尊源碼分析小家聊聊中的數據綁定屬性訪問器和實現類的使用小家聊聊中的數據綁定以及內省和對感興趣可掃碼加 每篇一句 不要總問低級的問題,這樣的人要么懶,不愿意上網搜索,要么笨,一點獨立思考的能力都沒有 相關閱讀 【小家Spring】聊聊Spring中的數據綁定 --- ...
摘要:你也會了解到構造對象的兩種策略。構造方法參數數量低于配置的參數數量,則忽略當前構造方法,并重試。通過默認構造方法創建對象看完了上面冗長的邏輯,本節來看點輕松的吧通過默認構造方法創建對象。 1. 簡介 本篇文章是上一篇文章(創建單例 bean 的過程)的延續。在上一篇文章中,我們從戰略層面上領略了doCreateBean方法的全過程。本篇文章,我們就從戰術的層面上,詳細分析doCreat...
閱讀 1386·2021-11-25 09:43
閱讀 3606·2021-11-10 11:48
閱讀 5180·2021-09-23 11:21
閱讀 1610·2019-08-30 15:55
閱讀 3519·2019-08-30 13:53
閱讀 1247·2019-08-30 10:51
閱讀 880·2019-08-29 14:20
閱讀 1986·2019-08-29 13:11