Spring Bean基礎

全是乾貨的技術號:

本文已收錄在github,歡迎 star/fork:java

https://github.com/Wasabi1234...git

Spring管理的這些bean藉由配置元數據建立,例如被@Bean註解。那麼在 Spring 內部又是如何存儲這些信息的呢?github

1 BeanDefinition

1.1 域

在容器內,這些bean定義被表示爲BeanDefinition對象,它包含但不限於以下元數據:api

這些元數據會轉換爲構成每一個bean定義內的一組屬性。

1.1.1 包限定類名

被定義bean的實際實現類數組

1.1.2 bean行爲

這些狀態指示bean在容器中的行爲(做用域、生命週期回調等)。以下即爲做用域:緩存

  • 默認的做用域也就是singleton

1.1.3 須要的其它bean引用

這些引用也就是常見的協做或依賴對象。bash

  • 例如對於以下類:

除了包含有關如何建立特定bean信息的bean定義外,ApplicationContext實現還容許註冊在容器外部(用戶自定義的)建立的現有對象。併發

這是經過getBeanFactory()方法訪問ApplicationContextBeanFactory完成的,該方法返回其DefaultListableBeanFactory實現。編碼

DefaultListableBeanFactory經過registerSingleton(..)registerBeanDefinition(..)方法支持此註冊。固然了,咱們開發的應用程序通常只使用經過常規的bean定義內的元數據定義的bean。spa

DefaultListableBeanFactory支持經過以下兩種方式進行註冊:

  • registerSingleton(String beanName, Object singletonObject)

bean實例就是傳遞給registerSingleton方法的singletonObject對象

  • registerBeanDefinition(String beanName, BeanDefinition beanDefinition)

容器根據BeanDefinition實例化bean

固然了,通常的應用程序仍是僅經過元數據定義的bean來定義bean。

Bean元數據和顯式編碼提供的單例實例需儘早地註冊,方便容器在自動裝配和其餘自省(指在運行時來判斷一個對象的類型的能力)過程能正確推理它們。雖然在某種程度上支持覆蓋現有的元數據或單例實例,但在運行時(與對工廠的實時訪問併發)對新bean的註冊並不被正式支持,而且可能致使併發訪問異常,好比bean容器中的狀態不一致。

2 如何給 bean 命名?

每一個bean都有一或多個標識符,這些標識符在其所在容器中必須惟一。一個bean一般只有一個標識符。但若它就是須要有一個以上的,那麼多餘標識符被視爲別名。

在bean定義中,可組合使用id、name 屬性指定bean的標識符。

  • 最多指定一個名稱的id屬性。通常來講,這些名字由字母數字組成(如myBean,fooService),但也可能包含特殊字符。
  • 若是還想爲bean引入其餘別名,可在name屬性指定任意數量的其餘名稱。用逗號,、分號;或空格分隔。

在Spring 3.1前,id屬性定義爲xsd:ID類型,該類型限制了可能的字符。從3.1開始,它被定義爲xsd:string類型。注意,Bean的id惟一性仍由容器強制執行,而再也不是XML解析器。

開發者無需提供bean的nameid。若是未明確提供,容器將爲該bean生成一個惟一name。但若是想經過使用ref元素或服務定位器模式查找來按名稱引用該bean,則必須提供一個name。不提供名稱的緣由和內部beans和自動裝配有關。

能夠爲bean提供多個名稱。這些名稱視做同一bean的別名,例如容許應用中的每一個組件經過使用特定於組件自己的bean名稱來引用公共依賴。

2.1 Bean命名規範

與對實例字段名稱的命名規範相同。即小寫字母開頭,後跟駝峯式大小寫。

示例:userServiceroleController

掃描類路徑下的組件,Spring就會按照該習慣爲未命名的組件生成bean名稱:將類名初始字符轉換爲小寫。其實這個規範便是JDK 裏的Introspector#decapitalize方法,Spring正使用了它:

decapitalize

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);

}

2.2 如何爲單個bean指定多個別名?

有時但願爲單個Bean提供多個名稱,尤爲是在多系統環境。

XML配置

可以使用<alias/>標籤:

<alias name="srcName" alias="extName"/>

定義別名後,可將同一容器中名爲srcName的bean稱爲extName

環境示例:

  • 子系統A的配置元數據可經過名稱subA-ds引用數據源
  • 子系統B可經過名稱subB-ds引用數據源
  • 使用這倆子系統的主系統經過名稱main-ds引用數據源。

要使全部三個名稱都引用相同的對象,可將如下別名定義添加到配置元數據:

<alias name="subA-ds" alias="subB-ds"/>

<alias name="subA-ds" alias="main-ds" />

如今,每一個組件和主應用程序均可以經過惟一名稱引用數據源,而且可保證不與任何其它定義衝突(等於高效建立了名稱空間),並且引用的是同一bean。

Java代碼配置

使用@Bean註解的name屬性接收一個String數組。示例以下:

@Configuration

public class AppConfig {

@Bean({"dataSource", "subA-ds", "subB-ds"})

public DataSource dataSource() {

// ...

}

}

3 如何實例化 bean?

BeanDefinition可看作是建立對象的配方。容器在被詢問時,會查看被命名過的bean的BeanDefinition,並使用該BeanDefinition中的配置元數據建立(或直接從緩存池獲取)對應的對象實例。

好比在XML方式下,在<bean/>標籤的class屬性指定要實例化的對象的類型。這個class屬性,其實就是BeanDefinition實例的Class屬性,所以該屬性通常強制必須指定。

可經過以下方式使用Class屬性來實例化 bean:

3.1 構造器

在容器自身經過反射調用其構造器直接建立bean時,指定要構造的bean類,相似new運算符。該方式下,類基本上都能被Spring兼容。即bean類無需實現任何特定接口或以特定方式編碼。 指定bean類便可。注意,根據所用的IoC類型,有時須要一個默認的無參構造器。

3.2 靜態工廠方法

指定包含將要建立對象的靜態工廠方法的實際類,容器將在類上調用靜態工廠方法以建立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;

}

}

3.3 實例工廠方法

使用該方式實例化會從容器中調用現有bean的非靜態方法來建立新bean。要使用此機制,需將class屬性置空,並在factory-bean屬性中,在當前(或父/祖先)容器中指定包含要建立該對象的實例方法的bean的名稱。factory-method設置工廠方法自己的名稱。

示例以下,來看看如何配置這樣的bean:

  • 相應的類:

  • 一個工廠類也能夠容納一個以上的工廠方法,以下:

這種方式還代表,即便是工廠bean也能夠經過依賴注入進行管理和配置。

「factory bean」是指在Spring容器中配置並經過實例或靜態工廠方法建立對象的bean。相比之下,FactoryBean是指特定於Spring的FactoryBean實現類。

4 如何肯定Bean的運行時類型?

bean元數據定義中的指定類只是初始類引用,可能結合使用的以下方式之一:

  • 聲明的工廠方法
  • FactoryBean類,該狀況可能致使bean的運行時類型不一樣
  • 實例級工廠方法(經過指定的factory-bean名稱解析),該狀況下直接就不設置了

所以,看起來肯定bean運行時類型絕非易事,該如何準確獲取呢?

BeanFactory.getType

推薦調用 BeanFactory.getType肯定bean的運行時類型。

該方法可肯定給定名稱bean的類型。 更確切地,返回針對相同bean名稱的BeanFactory.getBean調用將返回的對象的類型。

且該方法的實現考慮了前面窮舉的全部狀況,並針對於FactoryBean ,返回FactoryBean所建立的對象類型,和FactoryBean.getObjectType()返回一致。

相關文章
相關標籤/搜索