【小家Spring】Spring IoC是如何使用BeanWrapper和Java內省結合起來給Bean屬性賦值的

每篇一句

具有了技術深度,遇到問題能夠快速定位並從根本上解決。有了技術深度以後,學習其它技術能夠更快,再深刻其它技術也就不會懼怕java

相關閱讀

【小家Spring】聊聊Spring中的數據轉換:Converter、ConversionService、TypeConverter、PropertyEditor面試

【小家Spring】聊聊Spring中的數據綁定 --- 屬性訪問器PropertyAccessor和實現類DirectFieldAccessor的使用【小家Spring】聊聊Spring中的數據綁定 --- BeanWrapper以及Java內省Introspector和PropertyDescriptor架構

對Spring感興趣可掃碼加入wx羣: Java高工、架構師3羣(文末有二維碼)

前言

書寫此篇博文的原因是出自一道面試題:面試題目大概如標題所述。我我的認爲這道面試題問得是很是有水平的,由於它涉及到的知識點既有深度,又有廣度,可謂一箭雙鵰~~~所以在這裏分享給你們。app

爲了給此文作鋪墊,前面已經有兩篇文章分別敘述了Java內省和BeanWrapper,並且還分析了底層接口:屬性訪問器(PropertyAccessor)。若對此部分還不是很瞭解的話,建議能夠先出門左拐或者單擊【相關閱讀】裏的連接~框架

Spring IoC和Java內省的依賴關係說明

Spring須要依賴注入就須要使用BeanWrapper,上章節說了BeanWrapperImpl的實現大都委託給了CachedIntrospectionResults去完成,而CachedIntrospectionResults它的核心說法就是Java內省機制函數

從層層委託的依賴關係能夠看出,Spring IoC的依賴注入(給屬性賦值)是層層委託的最終給了Java內省機制,這是Spring框架設計精妙處之一。這也符合我上文所訴:BeanWrapper這個接口並不建議應用本身去直接使用~~~那麼本文就着眼於此,結合源碼去分析Spring IoC容器它使用BeanWrapper完成屬性賦值(依賴注入)之精華~源碼分析

Spring IoC中使用BeanWrapper源碼分析

Spring IoC我相信小夥伴並不陌生了,但IoC的細節不是本文的重點。爲了便於分析,我把這個過程畫一個時序圖描述以下:在這裏插入圖片描述有了這個簡略的時序圖,接下來就一步一步的分析吧post

doCreateBean() 建立Bean

任何建立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

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;
        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 中其餘體系是比較簡單的,它做爲BeanDefinitionBean轉換過程當中的中間產物,承載了 bean 實例的包裝、類型轉換、屬性的設置以及訪問等重要做用(請不要落了訪問這個重要能力)。

關於此面試題怎麼去回答,若是是我主考我會這麼評價回答:

  1. 能答到populateBean()這裏算是對這塊知識入門了
  2. 能答到applyPropertyValues()這裏,那基本對此回答就比較滿意了
  3. 固然若能答到:經過自定義實現一個轉換器+反射實現做爲實現,而繞過Java內省機制。那勢必就能夠加分了~
    1. 若達到自定義、個性化定義BeanDefinition這塊(雖然與本問題沒有必然關聯),也是能夠加分的(畢竟是面試而非考試~)

知識交流

若文章格式混亂,可點擊原文連接-原文連接-原文連接-原文連接-原文連接

==The last:若是以爲本文對你有幫助,不妨點個讚唄。固然分享到你的朋友圈讓更多小夥伴看到也是被做者本人許可的~==

**若對技術內容感興趣能夠加入wx羣交流:`Java高工、架構師3羣`。若羣二維碼失效,請加wx號:fsx641385712(或者掃描下方wx二維碼)。而且備註:"java入羣" 字樣,會手動邀請入羣**在這裏插入圖片描述

相關文章
相關標籤/搜索