品Spring:帝國的基石





生活是一杯酒,有時需要麻醉自己,才能夠暫時忘卻痛苦與不快。
生活是一杯茶,有時需要細細品味,才發現苦澀背后也會有甘甜。

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方法

bean定義可以繼承
String getParentName();

bean對應的類名稱,用來實例化bean
String getBeanClassName()
;

生命周期范圍
String getScope();

是否延遲實例化
boolean isLazyInit();

依賴的其它bean
String[] getDependsOn();

是否作為自動裝配候選bean
boolean isAutowireCandidate()
;

是否是主要的,用在可能有多個候選bean的情況
boolean isPrimary();

一個用來生成該bean的工廠bean名稱
String getFactoryBeanName();

一個用來生產該bean的工廠方法名稱
String getFactoryMethodName();

bean的構造函數
ConstructorArgumentValues getConstructorArgumentValues();

一些key/value,可以在bean實例化后設置給bean的屬性
MutablePropertyValues getPropertyValues();

初始化方法名稱
String getInitMethodName();

銷毀方法名稱
String getDestroyMethodName();

角色,應用層/基礎設施層
int getRole();

人類可讀的描述
String getDescription();

是否單例
boolean isSingleton();

是否原型
boolean isPrototype();

是否抽象
boolean isAbstract();


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

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

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


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

AnnotationMetadata getMetadata();

MethodMetadata getFactoryMethodMetadata();


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

String getClassName();

boolean isInterface();

boolean isAnnotation();

boolean isAbstract();

boolean isConcrete();

boolean isFinal();

boolean isIndependent();

boolean hasEnclosingClass();

String getEnclosingClassName();

boolean hasSuperClass();

String getSuperClassName();

String[] getInterfaceNames();

String[] getMemberClassNames();



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

String getMethodName();

String getDeclaringClassName();

String getReturnTypeName();

boolean isAbstract();

boolean isStatic();

boolean isFinal();

boolean isOverridable();



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

boolean isAnnotated(String annotationName);

Map<StringObject> getAnnotationAttributes(String annotationName);

Map<StringObject> getAnnotationAttributes(String annotationName, boolean classValuesAsString);

MultiValueMap<StringObject> getAllAnnotationAttributes(String annotationName);

MultiValueMap<StringObject> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);



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

Set<String> getAnnotationTypes();

Set<String> getMetaAnnotationTypes(String annotationName);

boolean hasAnnotation(String annotationName);

boolean hasMetaAnnotation(String metaAnnotationName);

boolean hasAnnotatedMethods(String annotationName);

Set<MethodMetadata> getAnnotatedMethods(String annotationName);




一個小示例


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

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

@Component
public class Boss {

}



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

public class Staff {

}

@Configuration
public class Company {

    @Bean
    public Staff littleMing() {
        return new Staff();
    }

    @Bean
    public Staff littleQiang() {
        return new Staff();
    }
}



在注冊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



(END)


作者是工作超過10年的碼農,現在任架構師。喜歡研究技術,崇尚簡單快樂。追求以通俗易懂的語言解說技術,希望所有的讀者都能看懂並記住。下面是公眾號和知識星球的二維碼,歡迎關注!

 

       


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM