序
生活是一杯酒,有時需要麻醉自己,才能夠暫時忘卻痛苦與不快。
生活是一杯茶,有時需要細細品味,才發現苦澀背后也會有甘甜。
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<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接口,用於獲取一個類上標有的注解信息:
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年的碼農,現在任架構師。喜歡研究技術,崇尚簡單快樂。追求以通俗易懂的語言解說技術,希望所有的讀者都能看懂並記住。下面是公眾號和知識星球的二維碼,歡迎關注!