全是乾貨的技術號:本文已收錄在github,歡迎 star/fork:java
Spring管理的這些bean藉由配置元數據建立,例如被@Bean
註解。那麼在 Spring 內部又是如何存儲這些信息的呢?github
在容器內,這些bean定義被表示爲BeanDefinition對象,它包含但不限於以下元數據:api
這些元數據會轉換爲構成每一個bean定義內的一組屬性。
被定義bean的實際實現類數組
這些狀態指示bean在容器中的行爲(做用域、生命週期回調等)。以下即爲做用域:緩存
singleton
這些引用也就是常見的協做或依賴對象。bash
除了包含有關如何建立特定bean信息的bean定義外,ApplicationContext
實現還容許註冊在容器外部(用戶自定義的)建立的現有對象。併發
這是經過getBeanFactory()
方法訪問ApplicationContext
的BeanFactory
完成的,該方法返回其DefaultListableBeanFactory
實現。編碼
DefaultListableBeanFactory
經過registerSingleton(..)
和registerBeanDefinition(..)
方法支持此註冊。固然了,咱們開發的應用程序通常只使用經過常規的bean定義內的元數據定義的bean。spa
DefaultListableBeanFactory支持經過以下兩種方式進行註冊:
bean實例就是傳遞給registerSingleton方法的singletonObject對象
容器根據BeanDefinition實例化bean
固然了,通常的應用程序仍是僅經過元數據定義的bean來定義bean。
Bean元數據和顯式編碼提供的單例實例需儘早地註冊,方便容器在自動裝配和其餘自省(指在運行時來判斷一個對象的類型的能力)過程能正確推理它們。雖然在某種程度上支持覆蓋現有的元數據或單例實例,但在運行時(與對工廠的實時訪問併發)對新bean的註冊並不被正式支持,而且可能致使併發訪問異常,好比bean容器中的狀態不一致。
每一個bean都有一或多個標識符,這些標識符在其所在容器中必須惟一。一個bean一般只有一個標識符。但若它就是須要有一個以上的,那麼多餘標識符被視爲別名。
在bean定義中,可組合使用id、name 屬性指定bean的標識符。
id
屬性。通常來講,這些名字由字母數字組成(如myBean,fooService),但也可能包含特殊字符。name
屬性指定任意數量的其餘名稱。用逗號,
、分號;
或空格分隔。在Spring 3.1前,id
屬性定義爲xsd:ID
類型,該類型限制了可能的字符。從3.1開始,它被定義爲xsd:string
類型。注意,Bean的id
惟一性仍由容器強制執行,而再也不是XML解析器。
開發者無需提供bean的name
或id
。若是未明確提供,容器將爲該bean生成一個惟一name
。但若是想經過使用ref
元素或服務定位器模式查找來按名稱引用該bean,則必須提供一個name
。不提供名稱的緣由和內部beans和自動裝配有關。
能夠爲bean提供多個名稱。這些名稱視做同一bean的別名,例如容許應用中的每一個組件經過使用特定於組件自己的bean名稱來引用公共依賴。
與對實例字段名稱的命名規範相同。即小寫字母開頭,後跟駝峯式大小寫。
示例:userService
,roleController
。
掃描類路徑下的組件,Spring就會按照該習慣爲未命名的組件生成bean名稱:將類名初始字符轉換爲小寫。其實這個規範便是JDK 裏的Introspector#decapitalize方法,Spring正使用了它:
java.beans.Introspector.decapitalize
public static String decapitalize(String name) { if (name == null || name.length() == 0) { return name; } // 若是有多個字符且第一和第二個字符均爲大寫字母 // 則會保留原始大小寫 if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) && Character.isUpperCase(name.charAt(0))){ return name; } // 使用簡單的類名,並將其初始字符轉換爲小寫 char chars[] = name.toCharArray(); chars[0] = Character.toLowerCase(chars[0]); return new String(chars); }
有時但願爲單個Bean提供多個名稱,尤爲是在多系統環境。
可以使用<alias/>
標籤:
<alias name="srcName" alias="extName"/>
定義別名後,可將同一容器
中名爲srcName
的bean稱爲extName
。
環境示例:
subA-ds
引用數據源subB-ds
引用數據源main-ds
引用數據源。要使全部三個名稱都引用相同的對象,可將如下別名定義添加到配置元數據:
<alias name="subA-ds" alias="subB-ds"/> <alias name="subA-ds" alias="main-ds" />
如今,每一個組件和主應用程序均可以經過惟一名稱引用數據源,而且可保證不與任何其它定義衝突(等於高效建立了名稱空間),並且引用的是同一bean。
使用@Bean
註解的name
屬性接收一個String數組。示例以下:
@Configuration public class AppConfig { @Bean({"dataSource", "subA-ds", "subB-ds"}) public DataSource dataSource() { // ... } }
BeanDefinition可看作是建立對象的配方。容器在被詢問時,會查看被命名過的bean的BeanDefinition,並使用該BeanDefinition中的配置元數據建立(或直接從緩存池獲取)對應的對象實例。
好比在XML方式下,在<bean/>
標籤的class
屬性指定要實例化的對象的類型。這個class
屬性,其實就是BeanDefinition實例的Class
屬性,所以該屬性通常強制必須指定。
可經過以下方式使用Class
屬性來實例化 bean:
在容器自身經過反射調用其構造器直接建立bean時,指定要構造的bean類,相似new運算符。該方式下,類基本上都能被Spring兼容。即bean類無需實現任何特定接口或以特定方式編碼。 指定bean類便可。注意,根據所用的IoC類型,有時須要一個默認的無參構造器。
指定包含將要建立對象的靜態工廠方法的實際類,容器將在類上調用靜態工廠方法以建立bean。
定義使用靜態工廠方法建立的bean時,可以使用class
屬性來指定包含靜態工廠方法的類,並使用factory-method
屬性指定工廠方法自己的名稱。開發者應該可以調用此方法並返回一個存活對象,該對象隨後將被視爲經過構造器建立的。
這種BeanDefinition的一種用法是在老代碼中調用static工廠。
看個例子,以下BeanDefinition指定將經過調用工廠方法來建立bean。該定義不指定返回對象的類型,而僅指定包含工廠方法的類。該示例中的initInstance()
方法須是靜態方法。
<bean id="serverService" class="examples.ServerService" factory-method="initInstance"/>
可與上面的BeanDefinition協同的類:
public class ServerService { private static ServerService serverService = new ServerService(); private ServerService() {} public static ServerService createInstance() { return serverService; } }
使用該方式實例化會從容器中調用現有bean的非靜態方法來建立新bean。要使用此機制,需將class
屬性置空,並在factory-bean
屬性中,在當前(或父/祖先)容器中指定包含要建立該對象的實例方法的bean的名稱。factory-method
設置工廠方法自己的名稱。
示例以下,來看看如何配置這樣的bean:
這種方式還代表,即便是工廠bean也能夠經過依賴注入進行管理和配置。
「factory bean」是指在Spring容器中配置並經過實例或靜態工廠方法建立對象的bean。相比之下,FactoryBean
是指特定於Spring的FactoryBean實現類。
bean元數據定義中的指定類只是初始類引用,可能結合使用的以下方式之一:
所以,看起來肯定bean運行時類型絕非易事,該如何準確獲取呢?
推薦調用 BeanFactory.getType
肯定bean的運行時類型。
該方法可肯定給定名稱bean的類型。 更確切地,返回針對相同bean名稱的BeanFactory.getBean
調用將返回的對象的類型。
且該方法的實現考慮了前面窮舉的全部狀況,並針對於FactoryBean ,返回FactoryBean所建立的對象類型,和FactoryBean.getObjectType()
返回一致。