品Spring:帝國的基石

算法 數據結構 Java XML 文章 設計 檔案 百度 程序員聖經 2019-07-10
作者:李新傑
來源:公眾號【編程新說】



生活是一杯酒,有時需要麻醉自己,才能夠暫時忘卻痛苦與不快。

生活是一杯茶,有時需要細細品味,才發現苦澀背後也會有甘甜。

Spring是一杯酒,一眼望不到邊的官方文檔,著實讓人難以下嚥。

Spring是一杯茶,在無邊的源碼中暢遊之後,發現色相味道俱全。

高考狀元是六月份的網紅,Spring帝國是Java界的明星。

狀元有自己的“武功祕籍”,Spring有自己的“帝國基石”。

請隨本文一起,品Spring,尋找帝國的基石。

帝國的基石

無論是大到一個國家,或是小到一個個人,都有自己賴以存在的基石。這個基石就是核心支柱,就像經濟基礎支撐著上層建築。

以BAT來說,百度的搜索,阿里的電商,騰訊的社交。可以說這是他們的立司之本,如果想在這些方面和他們PK,幾乎沒有勝算的可能。

Spring絕對是Java開發領域中一顆閃耀的明星,它的巨大光芒甚至一直在引領著Java的發展方向。

現在說它已經發展為一個帝國,應該不會有人站出來反對吧。嗯,站出來也沒關係,本人不接受反對。哈哈。

那麼有一個問題,請大家思考下,Spring帝國的基石是什麼?

用過或瞭解Spring的人肯定都會說是IoC啦,AOP啦,聲明式事務啦等等。只能說這些回答浮於表面,明顯不走心啊。

好了,我來公佈答案吧,這個帝國的基石,其實就是Bean。肯定會有人問,這個bean是什麼東西啊,那就去看它的定義吧。對,就是Spring中的bean定義。

在Spring中,bean定義其實就是一個接口,即BeanDefinition。我在上一篇“畢業十年”的文章中說過,我們定義的類或接口其實都是對一種數據構成的描述,所以可以直接把類或接口看作是一種數據結構。

那麼bean定義接口,就是一種數據結構,它記錄了一個bean的全部信息,後期Spring對這個bean的所有操作都是建立在這些信息之上的。

如果對Spring不是很熟悉的朋友,聽到“bean的全部信息”這句話會有點懵。不要擔心,照例拿生活中我們熟悉的事物去做類比,爭取讓所有人都能明白。

在醫療行業,每個患者都會有一個病歷,上面記錄了患者家族病史,患者個人病史,都做過哪些檢查以及檢查結果,都做過哪些治療以及恢復情況。還有大夫每次對患者的病情診斷與分析。

這些信息肯定是記錄的越全面越好,後續的治療方案都是依賴這些信息而制定的。Spring中bean的信息就對等於這裡患者的病歷信息。

在公安系統,每個嫌疑人也會有一個檔案,上面記錄了他的口供,作案信息或一些其它證據,同樣這些信息蒐集的越全面越好,後期法官的宣判與量刑也都依賴於它。

那麼在這裡,記錄案件信息的檔案,就可以對等於Spring中bean的信息。

相信通過這兩個示例,你已經完全明白了這個bean信息的作用和地位。雖然到目前為止,你可能還真不知道它裡面到底存儲的是什麼信息。但這不要緊,只要記住它非常重要就可以了。

趁著這個機會,再小小拓展一下:

這裡的病歷信息和檔案信息裡面記錄的都是一些數據,所以可以認為它們對應於程序中的數據結構。

醫生的治療方案和法官的宣判,其實都是依賴這些數據做出的決定,因此可以認為它們對應於程序中的算法。

可見,數據結構決定著算法,或者說,算法是基於數據結構而設計的。

因此,可以說數據結構的重要性要大於算法。良好的數據結構能簡化算法,不好的數據結構只能使算法變得更復雜。

跟著變化走,把它當朋友

在上篇文章中提到過,唯一不變的就是變化,所以隨著時間的推移,只需不斷往這個數據結構中補充新的bean信息,Spring再利用這些補充信息去定義新的操作,以適應發展的需要。

就是這樣,Spring一步一步成長為一個浩浩蕩蕩的帝國。就像我在上一遍文章中說的,類或接口這樣的數據結構一定要進行精心設計,這樣代碼寫起來會簡單些,而且後期改起來也會容易些。

一個非常明顯的例子,一開始都是基於XML配置文件的,現在都是基於註解或Java配置的,可以說Spring完成了一次華麗的轉身,而且非常完美絲滑,沒有一點拖泥帶水。

其實就是在bean定義數據結構中加入了註解和Java配置相關的信息,Spring利用這些信息去重新實現一遍,並且和基於XML的實現並存,因此既可以用XML也可以用註解。

就像我在上一篇文章中說的,一定要合理抽象,從宏觀整體把握,良好定義整體架構或結構,至於一些具體的局部實現細節,可以根據實際情況來定。

因為局部實現涉及範圍一般較小,後期換用新的方式來個重新實現也會相對容易一些。從XML到註解基本就是這樣子的。

其實說實話,上一篇文章就是從這一篇分離出去的,專門為本篇文章埋伏筆、做鋪墊用的。哈哈。

滔滔不絕的說了這麼多,快來看看廬山真面目吧。

最討厭的就是源碼

有句話是怎麼說的呢,“要不是為了生活,誰願意把自己弄得滿身才華”。哈哈,看源碼時多想想這句話。

不想看的,直接跳過吧。

BeanDefinition接口,及bean定義,下面只列出了get方法,其實還有set方法:

品Spring:帝國的基石

品Spring:帝國的基石

這兩點比較關鍵,需要知道:

可以有兩種方法來指定一個bean的定義,一個是類名稱,一個是工廠方法。

單例和原型這兩種生命週期不是互斥關係,因為存在既不是單例也不是原型的,如request、session等範圍。

AnnotatedBeanDefinition接口,擴展了bean定義接口,增加了註解相關信息:

AnnotationMetadata getMetadata();
MethodMetadata getFactoryMethodMetadata();


ClassMetadata接口,是通過類註冊時,和類相關的一些信息:

品Spring:帝國的基石

MethodMetadata接口,是通過工廠方法註冊時,和方法相關的信息:

品Spring:帝國的基石

AnnotatedTypeMetadata接口,用於獲取註解的屬性信息:

boolean isAnnotated(String annotationName);
Map<String, Object> getAnnotationAttributes(String annotationName);
Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString);
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName);
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);


AnnotationMetadata接口,用於獲取一個類上標有的註解信息:

品Spring:帝國的基石

一個小示例

上面的東西太抽象了,下面通過一個簡單的示例,來具體看下。

使用@Component註解註冊一個Boss類的bean定義。

@Component
public class Boss {
}


使用@Configuration類裡的@Bean方法註冊兩個Staff的bean定義,同時Company類的bean定義也會被註冊。

品Spring:帝國的基石

在註冊bean定義時,需要一個bean名稱,默認會自動生成,就是首字母小寫的類名或方法名。

因此,以上四個bean定義的名稱分別是:

boss
company
littleMing
littleQiang


既然已經到這裡了,就應該滿足一下好奇心,取出bean定義看看,是什麼樣子。

下面是Boss類的bean定義:

bossBD = Generic bean: class [org.cnt.ts.bean.Boss]; 
scope=singleton; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false;
factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null;
defined in file [G:\workspace\sts4-cnt\taste-spring\target\classes\org\cnt\ts\bean\Boss.class],
-> class org.springframework.context.annotation.ScannedGenericBeanDefinition


可以看出類名稱就是Boss類的全名,因此它是通過類註冊的,所以工廠bean的名稱和工廠方法的名稱都是null。

下面是Company類的bean定義:

companyBD = Generic bean: class [org.cnt.ts.bean.Company$$EnhancerBySpringCGLIB$$d6437e9f]; 
scope=singleton; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false;
factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null;
defined in file [G:\workspace\sts4-cnt\taste-spring\target\classes\org\cnt\ts\bean\Company.class],
-> class org.springframework.context.annotation.ScannedGenericBeanDefinition


可以看出類名稱就是Company類的全名,不過已經被CGLIB增強過了。因此它是通過類註冊的,所以工廠bean的名稱和工廠方法的名稱都是null。

下面是小明這個Staff類的bean定義:

littleMingBD = Root bean: class [null]; 
scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false;
factoryBeanName=company; factoryMethodName=littleMing; initMethodName=null; destroyMethodName=(inferred);
defined in class path resource [org/cnt/ts/bean/Company.class],
-> class org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader$ConfigurationClassBeanDefinition


可以看出類名是null,說明是通過工廠方法註冊的,即company工廠類的littleMing工廠方法。

下面是小強這個Staff類的bean定義:

littleQiangBD = Root bean: class [null]; 
scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false;
factoryBeanName=company; factoryMethodName=littleQiang; initMethodName=null; destroyMethodName=(inferred);
defined in class path resource [org/cnt/ts/bean/Company.class],
-> class org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader$ConfigurationClassBeanDefinition


可以看出類名是null,說明是通過工廠方法註冊的,即company工廠類的littleQiang工廠方法。

小名和小強的註冊方式完全一樣,而且都是Staff類,我們應該有看看它們是否相同的好奇心。

littleMingBD == littleQiangBD -> false
littleMingBD equals littleQiangBD -> false


發現這兩個bean定義既不是相同,也不是相等。

現在都是基於註解的,自然可以獲取到類上標的註解的信息。

Boss類上是@Component註解:

bossAnno = {value=}


Company類上是@Configuration註解:

companyAnno = {value=}


littleMing()方法上是@Bean註解:

littleMingAnno = {name=[], value=[], initMethod=, autowireCandidate=true, autowire=NO, destroyMethod=(inferred)}


littleQiang()方法上是@Bean註解:

littleQiangAnno = {name=[], value=[], initMethod=, autowireCandidate=true, autowire=NO, destroyMethod=(inferred)}


因為我們沒有設置註解的屬性,所以上面四個註解都是默認值。

本文主要講的是bean定義,切記,bean定義和bean實例(或叫bean對象)可不是一碼事,別搞混了。

其實我們日常的業務開發和知不知道bean定義是啥東西關係真不大,就像我們平時吃喝拉撒一樣,只要會張開嘴吃喝就行了,至於食物在體內如何消化吸收、產生廢物完全不用知道。

但是要想活的健康、要想養生,必須要知道這些,同理,要想做一個有追求、有夢想的程序員,也需要知道bean定義。

如果一個人沒有夢想,那跟鹹魚有什麼區別。

示例代碼:

https://github.com/coding-new-talking/taste-spring.git

相關推薦

推薦中...