具有了技術深度,遇到問題能夠快速定位並從根本上解決。有了技術深度以後,學習其它技術能夠更快,再深刻其它技術也就不會懼怕java
【小家Spring】聊聊Spring中的數據轉換:Converter、ConversionService、TypeConverter、PropertyEditor面試
【小家Spring】聊聊Spring中的數據綁定 --- 屬性訪問器PropertyAccessor和實現類DirectFieldAccessor的使用
【小家Spring】聊聊Spring中的數據綁定 --- BeanWrapper以及Java內省Introspector和PropertyDescriptorspring
書寫此篇博文的原因是出自一道面試題:面試題目大概如標題所述。
我我的認爲這道面試題問得是很是有水平的,由於它涉及到的知識點既有深度,又有廣度,可謂一箭雙鵰~~~所以在這裏分享給你們。架構
爲了給此文作鋪墊,前面已經有兩篇文章分別敘述了Java內省和BeanWrapper
,並且還分析了底層接口:屬性訪問器(PropertyAccessor
)。若對此部分還不是很瞭解的話,建議能夠先出門左拐或者單擊【相關閱讀】裏的連接~app
Spring須要依賴注入就須要使用BeanWrapper
,上章節說了BeanWrapperImpl
的實現大都委託給了CachedIntrospectionResults
去完成,而CachedIntrospectionResults
它的核心說法就是Java內省機制。框架
從層層委託的依賴關係能夠看出,Spring IoC
的依賴注入(給屬性賦值)是層層委託的最終給了Java內省機制,這是Spring框架設計精妙處之一。這也符合我上文所訴:BeanWrapper
這個接口並不建議應用本身去直接使用~~~
那麼本文就着眼於此,結合源碼去分析Spring IoC容器它使用BeanWrapper
完成屬性賦值(依賴注入)之精華~函數
BeanWrapper
源碼分析Spring IoC
我相信小夥伴並不陌生了,但IoC
的細節不是本文的重點。爲了便於分析,我把這個過程畫一個時序圖描述以下:
有了這個簡略的時序圖,接下來就一步一步的分析吧源碼分析
任何建立Bean的過程,都得經歷doCreateBean()
。這句代碼咱們已經很是熟悉了,它在AbstractAutowireCapableBeanFactory
裏:post
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()
這個方法,它的做用是完成給屬性賦值,從時序圖中也能夠看出這是一個入口學習
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()
這裏,那基本對此回答就比較滿意了固然若能答到:經過自定義實現一個轉換器+反射實現做爲實現,而繞過Java內省機制。那勢必就能夠加分了~
1. 若達到自定義、個性化定義BeanDefinition
這塊(雖然與本問題沒有必然關聯),也是能夠加分的(畢竟是面試而非考試~)
若文章格式混亂,可點擊
:原文連接-原文連接-原文連接-原文連接-原文連接
==The last:若是以爲本文對你有幫助,不妨點個讚唄。固然分享到你的朋友圈讓更多小夥伴看到也是被做者本人許可的~
==
若對技術內容感興趣能夠加入wx羣交流:Java高工、架構師3羣
。
若羣二維碼失效,請加wx號:fsx641385712
(或者掃描下方wx二維碼)。而且備註:"java入羣"
字樣,會手動邀請入羣