深入 Spring IoC - 3 Bean 概覽

Bean 概覽

Spring IoC 容器可以管理多個bean.這些bean是通過你提供的配置元數據生成的(比如,是用XML<bean/> 定義).

在容器內部,這些bean定義表示為 BeanDefinition 對象, 包含了(除了其他信息外)以下元數據:

  • 包限定類名: 通常是定義bean的實際實現類
  • bean 行為配置元素, 聲明了bean在容器中的行為(作用域,生命周期回調,等等)
  • 這個 bean 工作所需的其他 bean 的引用. 這些引用也被稱作合作者活著依賴.
  • 新創建對象的一些其他配置設置. 比如, 池的大小限制活著連接數量等在一個管理連接池的 bean 中需要使用的參數.

元數據里的配置最后都會轉換成 bean 的屬性

除了包含如何創建一個指定bean 的bean定義之外, ApplicationContext 實現還允許將容器之外創建的對象進行注冊.這個操作是通過 getBeanFactory() 方法來訪問 ApplicationContext 的 BeanFactory 來實現的, 這個操作會返回一個 DefaultListableBeanFactory 實現. DefaultListableBeanFactory 支持通過 registerSingleton(..)registerBeanDefinition(..) 來注冊. 然而, 通常應用只需要通過常規 bean 元數據 定義就可以了.

Bean 元數據和手動提供單例實例要盡可能的早,這樣可以使得容器在自動裝配和其他內部檢測步驟中正確的推斷出他們.盡管在某種程度上重寫已存在的元數據和單例實例是支持的,在運行時注冊新的bean(和實時訪問工廠同時發生)官方是不支持的,這會導致并發訪問異常,Bean容器中的狀態不一致,或者兩者都有.

Bean命名

每個 bean 都有一個或多個標識.這些標識在持有他們的容器中必須是唯一的.通常情況下一個 bean 只有一個標識.然而,如果他需要更多標識,其他的標識可以當作是別名.

在基于 XML 的配置元數據中,你可以使用 id 屬性, name 屬性,或者都使用來指定bean的標識. id 屬性允許你指定一個 id. 通常這些名字是字母(“myBean”,等等),但是他們可以包含特殊字符. 如果你想要給這個bean 使用其他的別名, 你可以使用 name 屬性來指定,使用 , , ; ,或者空格來隔開.注意 bean id 的唯一性由容器來保證,不再由 XML 解析器檢測.

你不一定要提供 name 或者 id 給bean. 如果你不顯式的提供 idname , 容器會生成一個唯一的名字給它. 然而,如果你想要通過名字來獲取bean , 通過使用 ref 元素或者是一個服務定位器風格的查找 , 你必須要提供一個名字. 一般情況下不提供一個關聯的名字的原因是使用了 inner beans and autowiring collaborators.

Bean 命名風格

通常在命名 bean 的時候是使用標準Java 規范中給實例字段命名的風格.也就是 bean 命名以小寫字母開頭并使用駝峰風格.比如 accountManager, accountService, userDao 等.

保持 bean 命名的一致性使得你的配置更容易閱讀和理解.同樣,如果你使用了 Spring AOP, 這在要對 bean 使用 advice 的時候是很有用的.

在類路徑中使用組件掃描的時候,Spring 會為未命名組件生成bean 名字,遵循前面描述的規則: 使用簡單類名并將它的首字母改為小寫. 然而,在特殊情況下可能頭兩個字母都是大寫,這個時候就保留原來的大寫. 這和 java.beans.Introspector.decapitalize 的規則一致. 原因推測是由于如果開頭多個連續大寫一般是這個詞語是特殊名詞,所以保留風格.

在 bean 定義外定義別名

在 bean 定義本身中, 你可以給 bean 多個名字, 通過在 id 屬性中指定一個名字和 name 屬性中指定任意個數的名字. 這些名字對于這個 bean 來說都是等價的別名并且在某些情況下很有用, 比如為每個組件都引入一個公共依賴但是這個依賴名字對于每個組件而言又是特定的. 也就是提高了非侵入性,使得組件復用度提高,而不需要讓應用的容器中 bean 定義被綁死.

然而,只在bean 定義中指定別名不一定能應付所有場景.有時候我們想要在其它地方定義別名. 這在配置被分散到各個子系統的大型系統中是很常見的,每個子系統都有他們自己的對象定義. 在基于 XML 的配置元數據中,你可以使用 <alias/> 元素來做到. 如下:

<alias name="fromName" alias="toName"/>

在這種情況下, 一個叫做 fromName 的bean (在同一個容器下) 在使用了這個別名定義之后,也可以使用 toName 引用.

比如,子系統 A 的配置元數據可能使用 subsystemA-dataSource 來引用一個數據源. 子系統 B 中的配置元數據可能使用名字 subsystemB-dataSource 來引用一個數據源. 當將這兩個子系統組合成一個應用的時候,主應用使用名字 myApp-dataSource 來引用數據源. 為了讓這三個名字引用到相同的對象,你可以添加以下別名定義到配置元數據:

<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>

現在每個和主應用可以通過一個唯一的名字來引用到數據源,并且保證不會和其他定義沖突,而且引用的還是相同的bean.

Java 配置

如果你使用的是 Java 配置, @Bean 注解也可以使用別名, 見 Using the @Bean Annotation 獲得細節

Bean 實例化

一個bean 定義本質上是創建對象的方法.容器根據bean 名字查找對應的方法并使用bean定義中封裝的配置元數據來創建(或者 獲取)一個實際的對象.

如果你使用的是基于 XML 的配置元數據,你通常必須在 <bean/> 元素中的 class 屬性中指定對象的類型(或者class).這個 class 屬性(實際上是 BeanDefinition 實例的 Class 屬性 ) .(特殊情況可以看 Instantiation by Using an Instance Factory Method and Bean Definition Inheritance.) 你可以通過兩種方式使用 Class 屬性:

  • 通常情況下,指定bean 要構造的類是為了在容器通過反射調用構造器的時候使用,等同于 Java 代碼中的 new 操作符
  • 用來指定靜態工廠方法創建對象的實際類,偶爾容器調用靜態工廠方法來創建一個bean.這個靜態工廠方法返回的類型可能是相同的類或者是另外一個類

內部類命名

如果你想要配置一個靜態內部類的bean 定義,你必須使用內部類的 binary 名字.

比如, 你有一個類叫做 SomeThingcom.example 包下,并且這個類擁有一個內部類叫做 OtherThing ,這個類的bean 定義 class 屬性值應該是 com.example.SomeThing$OtherThing.

注意要使用 $ 連接內部類和外部類

使用構造函數實例化

當你使用構造函數的方式來創建bean,所有正常的類都可以被Spring使用和兼容.也就是說,開發的類不需要實現任何制定的接口或者是使用特別的方式來編碼.簡單的指定bean就可以了.然而,根據你使用什么類型的IoC ,你需要一個默認(空)構造器.

Spring IoC 容器可以管理你想要被管理的所有類.不僅僅是管理真正的JavaBean.大多數用戶喜歡只有一個默認(無參)構造器并且適量的getter和setter的 JavaBean.你也可以在容器里放入一些不太符合bean風格的類.比如,你需要使用一個常規的連接池它并不符合一個JavaBean的規范,Spring也可以管理它.

使用XML 配置元數據如下:

<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>

給構造器添加參數的機制以及在對象構造后設置實例對象的屬性的內容可以看 Injecting Dependencies.

通過靜態工廠方法實例化

當使用靜態工廠方法定義一個bean的時候,使用 class 屬性來指定包含 static 工廠方法的類并且使用factory-method 來指定工廠方法的名字.這個方法應該可以直接調用(可以帶有一些參數,后面會介紹)并且返回一個可用對象,后面就可以將這個對象當作通過構造器生成的一樣使用.這個bean定義也叫做靜態工廠方法.

下面的bean定義指定了一個bean使用工廠方法生成.這個定義并沒有指定返回對象的類型(類),只有包含了工廠方法的類.在這個例子中 createInstance() 必須是一個靜態方法.

<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>

下面是一個符合這個定義的類

public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}

class ClientService private constructor() {
    companion object {
        private val clientService = ClientService()
        fun createInstance() = clientService
    }
}

對于提供可選的參數給工廠方法并且在工廠返回對象后設置實例屬性的詳細內容見 Dependencies and Configuration in Detail.

通過實例工廠方法來實例化

和通過靜態工廠方法實例化類似,通過實例工廠方法實例化是通過調用容器中一個已存在bean的非靜態方法來生成一個新的bean.為了使用這個機制,將 class 屬性放空,并且在 factory-bean 屬性中指定當前容器(或者父容器或祖先容器)包含了要調用的生成對象的實例工廠方法的bean名稱.在 factory-method 它的工廠方法的名稱.下面展示了如何配置這樣的bean:

<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<!-- the bean to be created via the factory bean -->
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

對應的類如下:

public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}

class DefaultServiceLocator {
    companion object {
        private val clientService = ClientServiceImpl()
    }
    fun createClientServiceInstance(): ClientService {
        return clientService
    }
}

一個工廠類可以同時持有多個工廠方法,直接復制使用一個的方式即可

這個方式展示了工廠bean的配置和管理可以使用 依賴注入.見 Dependencies and Configuration in Detail.

在Spring 中, “工廠bean” 指的是一個bean配置在Spring容器中并且通過實例或者靜態工廠方法創建了對象. FactoryBean 指的是Spring中的一個類.

判斷一個bean 的運行時類型

判斷一個指定bean的運行時類型并不是一件簡單的事.一個bean定義中的指定類只是一個初始類引用,潛在的和一個工廠方法或者 FactoryBean 組合可能會使得這個類的運行時類型是不同的.或者是在定義中完全沒有指定類的初始引用比如使用實例工廠方法,它只指定了 factory-bean 的名字.此外,AOP代理可能會將bean實例包裹在一個基于接口的代理這會限制了目標bean暴露它實際的類型.

推薦對指定bean名稱使用 BeanFactory.getType 來獲取實際運行時類型.這個方式考慮到了上面的所有情況并且返回對象的類型,而 BeanFactory.getBean 調用返回了相同的bean名字.

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容