1. 程式人生 > >【小家Spring】Spring IoC是如何使用BeanWrapper和Java內省結合起來給Bean屬性賦值的

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

每篇一句

具備了技術深度,遇到問題可以快速定位並從根本上解決。有了技術深度之後,學習其它技術可以更快,再深入其它技術也就不會害怕

相關閱讀

【小家Spring】聊聊Spring中的資料轉換:Converter、ConversionService、TypeConverter、PropertyEditor

【小家Spring】聊聊Spring中的資料繫結 --- 屬性訪問器PropertyAccessor和實現類DirectFieldAccessor的使用
【小家Spring】聊聊Spring中的資料繫結 --- BeanWrapper以及Java內省Introspector和PropertyDescriptor


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

前言

書寫此篇博文的緣由是出自一道面試題:面試題目大概如標題所述。
我個人認為這道面試題問得是非常有水平的,因為它涉及到的知識點既有深度,又有廣度,可謂一舉兩得~~~因此在這裡分享給大家。

為了給此文做鋪墊,前面已經有兩篇文章分別敘述了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的細節不是本文的重點。為了便於分析,我把這個過程畫一個時序圖描述如下:

有了這個簡略的時序圖,接下來就一步一步的分析吧

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()這個方法,它的作用是完成給屬性賦值,從時序圖中也可以看出這是一個入口

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入群" 字樣,會手動邀請入群

相關推薦

SpringSpring IoC是如何使用BeanWrapperJava內省結合起來Bean屬性

每篇一句 具備了技術深度,遇到問題可以快速定位並從根本上解決。有了技術深度之後,學習其它技術可以更快,再深入其它技術也就不會害怕 相關閱讀 【小家Spring】聊聊Spring中的資料轉換:Converter、ConversionService、TypeConverter、PropertyEditor 【

思想通俗易懂版講解JWTOAuth2,以及他倆的區別聯絡(Token鑑權解決方案)

相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9

SQLMySql資料型別---日期時間型別的使用(含datetimetimestamp的區別)

每篇一句 練武不練功,到老一場空。 程式設計師應該注重內功的修煉,那才是核心競爭力 說在前面 在這一路學習過來,每次不管看書還是網上看的資料,對於MySQL資料型別中的時間日期型別總是一掃而過,不曾停下來認認真真的研究學習。最近看了一本關於MySql的書

馬哥Spring Boot 、Spring Cloud系列講座

系列套餐 講座大綱 講座大綱 作者:杜琪 連結:https://www.jianshu.com/p/e35427e025b3 來源:簡書 簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。

面試螞蟻金服(2018年)Java一面面試題

相關閱讀 廢話不多說,直接把印象中的面試題貼出來: 雖然我自己沒有完全答得很好,但本文給出一些參考性的答案。如果覺得不妥的,可以給我留言討論 1、自我介紹、自己做的專案和技術領域 略 2、專案中的監控:常見的監控指標有哪些? QPS、進出口流量、C

java使用反射物件屬性

public class Rwhc implements Comparator<Rwhc> { private int id; //id private String qihao; //期號 private String kjh;

Spring藉助Springfox整合Swagger(API介面神器)SpringBoot

背景 隨著網際網路技術的發展,現在的網站架構基本都由原來的後端渲染,變成了:前端渲染、先後端分離的形態,而且前端技術和後端技術在各自的道路上越走越遠。 前端和後端的唯一聯絡,變成了API介面;API文件變成了前後端開發人員聯絡的紐帶,變得越來越重要,swagger就是一款讓你更好

SpringSpring AOP的多種使用方式以及神一樣的AspectJ-AOP使用介紹

相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9

javaSpring AOP的多種使用方式以及神一樣的AspectJ-AOP使用介紹

相關閱讀 什麼時候AOP AOP(Aspect-OrientedProgramming,面向方面程式設計),可以說是OOP(Object-Oriented Programing,面向物件程式設計)的補充和完善。 AOP技它利用一種稱為“橫切”的技術,剖解開封

Spring老專案遷移問題:@ImportResource匯入的xml配置裡的Bean能夠使用@PropertySource匯入的屬性嗎?

#### 每篇一句 > 大師都是偏執的,偏執才能產生力量,妥協是沒有力量的。你對全世界妥協了你就是空氣。所以若沒有偏見,哪來的大師呢 #### 相關閱讀 [【小家Spring】詳解PropertyPlaceholderConfigurer、PropertyOverrideConfigurer等對屬性配置檔案

SFA官方翻譯Spring WebFluxSpring Cloud進行響應式微服務開發

啟用 測試數據 技術 logger 轉發 bic snap uri led 原創 SpringForAll社區 2018-05-18 作者 Spring4all 社區 摘要: 如果你想用Spring的最新和最好的工具開始使用響應式微服務,那麽這篇文章就是

javaPOP(面向過程程式設計)、OOP(面向物件程式設計)、AOP(面向切面程式設計)三種程式設計思想的區別聯絡

相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9

javaSessionCookie的區別聯絡、分散式session的幾種實現方式

相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9

javaJava8新特性之---CompletableFuture的系統講解例項演示(使用CompletableFuture構建非同步應用)

相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9

javaJava中主執行緒(父執行緒)與子執行緒的通訊聯絡

相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9

Java深入理解Java列舉型別(enum)及7種常見的用法(含EnumMapEnumSet)

相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9

Java一次Java執行緒池誤用(newFixedThreadPool)引發的線上血案總結

相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9

javaSortedMapNavigableMap的使用介紹---TreeMap的原始碼簡單分析

相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9

SpringMVC學習04Spring、MyBatisSpringMVC的整合

前兩篇springmvc的文章中都沒有和mybatis整合,都是使用靜態資料來模擬的,但是springmvc開發不可能不整合mybatis,另外mybatis和spring的整合我之前學習mybatis的時候有寫過一篇,但是僅僅是整合mybatis和spring,所以這篇文

java類中靜態程式碼塊、構造程式碼塊、靜態變數執行順序繼承邏輯

相關閱讀 每篇一句 上帝給每個人都安排了幸福的一生,我們的任務就是把它走完 1、概述 誠如各位所知,java的三大特性:封裝、繼承、多型。其中繼承,是java中最有學問的一點也是最相對來說最難理解的一些東西,本文針對於此,做一些例項分析,希望能夠幫助大家