【小家Spring】Spring IoC是如何使用BeanWrapper和Java內省結合起來給Bean屬性賦值的
每篇一句
具備了技術深度,遇到問題可以快速定位並從根本上解決。有了技術深度之後,學習其它技術可以更快,再深入其它技術也就不會害怕
相關閱讀
【小家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中使用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 中其他體系是比較簡單的,它作為BeanDefinition
向 Bean
轉換過程中的中間產物,承載了 bean 例項的包裝、型別轉換、屬性的設定以及訪問等重要作用(請不要落了訪問這個重要能力)。
關於此面試題怎麼去回答,如果是我主考我會這麼評價回答:
- 能答到
populateBean()
這裡算是對這塊知識入門了 - 能答到
applyPropertyValues()
這裡,那基本對此回答就比較滿意了 當然若能答到:通過自定義實現一個轉換器+反射實現作為實現,而繞過Java內省機制。那勢必就可以加分了~
1. 若達到自定義、個性化定義BeanDefinition
這塊(雖然與本問題沒有必然關聯),也是可以加分的(畢竟是面試而非考試~)知識交流
若文章格式混亂,可點選
:原文連結-原文連結-原文連結-原文連結-原文連結
==The last:如果覺得本文對你有幫助,不妨點個讚唄。當然分享到你的朋友圈讓更多小夥伴看到也是被作者本人許可的~
==
若對技術內容感興趣可以加入wx群交流:Java高工、架構師3群
。
若群二維碼失效,請加wx號:fsx641385712
(或者掃描下方wx二維碼)。並且備註:"java入群"
字樣,會手動邀請入群
相關推薦
【小家Spring】Spring IoC是如何使用BeanWrapper和Java內省結合起來給Bean屬性賦值的
每篇一句 具備了技術深度,遇到問題可以快速定位並從根本上解決。有了技術深度之後,學習其它技術可以更快,再深入其它技術也就不會害怕 相關閱讀 【小家Spring】聊聊Spring中的資料轉換:Converter、ConversionService、TypeConverter、PropertyEditor 【
【小家思想】通俗易懂版講解JWT和OAuth2,以及他倆的區別和聯絡(Token鑑權解決方案)
相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9
【小家SQL】MySql資料型別---日期時間型別的使用(含datetime和timestamp的區別)
每篇一句 練武不練功,到老一場空。 程式設計師應該注重內功的修煉,那才是核心競爭力 說在前面 在這一路學習過來,每次不管看書還是網上看的資料,對於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就是一款讓你更好
【小家Spring】Spring AOP的多種使用方式以及神一樣的AspectJ-AOP使用介紹
相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9
【小家java】Spring AOP的多種使用方式以及神一樣的AspectJ-AOP使用介紹
相關閱讀 什麼時候AOP AOP(Aspect-OrientedProgramming,面向方面程式設計),可以說是OOP(Object-Oriented Programing,面向物件程式設計)的補充和完善。 AOP技它利用一種稱為“橫切”的技術,剖解開封
【小家Spring】老專案遷移問題:@ImportResource匯入的xml配置裡的Bean能夠使用@PropertySource匯入的屬性值嗎?
#### 每篇一句 > 大師都是偏執的,偏執才能產生力量,妥協是沒有力量的。你對全世界妥協了你就是空氣。所以若沒有偏見,哪來的大師呢 #### 相關閱讀 [【小家Spring】詳解PropertyPlaceholderConfigurer、PropertyOverrideConfigurer等對屬性配置檔案
【SFA官方翻譯】Spring WebFlux和Spring Cloud進行響應式微服務開發
啟用 測試數據 技術 logger 轉發 bic snap uri led 原創 SpringForAll社區 2018-05-18 作者 Spring4all 社區 摘要: 如果你想用Spring的最新和最好的工具開始使用響應式微服務,那麽這篇文章就是
【小家java】POP(面向過程程式設計)、OOP(面向物件程式設計)、AOP(面向切面程式設計)三種程式設計思想的區別和聯絡
相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9
【小家java】Session和Cookie的區別和聯絡、分散式session的幾種實現方式
相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9
【小家java】Java8新特性之---CompletableFuture的系統講解和例項演示(使用CompletableFuture構建非同步應用)
相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9
【小家java】Java中主執行緒(父執行緒)與子執行緒的通訊和聯絡
相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9
【小家Java】深入理解Java列舉型別(enum)及7種常見的用法(含EnumMap和EnumSet)
相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9
【小家Java】一次Java執行緒池誤用(newFixedThreadPool)引發的線上血案和總結
相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9
【小家java】SortedMap和NavigableMap的使用介紹---TreeMap的原始碼簡單分析
相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9
【SpringMVC學習04】Spring、MyBatis和SpringMVC的整合
前兩篇springmvc的文章中都沒有和mybatis整合,都是使用靜態資料來模擬的,但是springmvc開發不可能不整合mybatis,另外mybatis和spring的整合我之前學習mybatis的時候有寫過一篇,但是僅僅是整合mybatis和spring,所以這篇文
【小家java】類中靜態程式碼塊、構造程式碼塊、靜態變數執行順序和繼承邏輯
相關閱讀 每篇一句 上帝給每個人都安排了幸福的一生,我們的任務就是把它走完 1、概述 誠如各位所知,java的三大特性:封裝、繼承、多型。其中繼承,是java中最有學問的一點也是最相對來說最難理解的一些東西,本文針對於此,做一些例項分析,希望能夠幫助大家