Spring Framework Core(1)-The Ioc Container(1) Spring IoC 容器和Beans簡介

IoC 容器

1.1 介紹Spring IoC 容器和Beans

本章介紹了控制反轉(IoC)原理的Spring框架實現。IoC也稱爲依賴項注入(DI)。這是一個對象僅通過構造函數參數、工廠方法的參數或對象實例構造或從工廠方法返回後在對象實例上設置的屬性來定義其依賴項(即使用的其他對象)的過程。然後容器在創建bean時注入這些依賴項。這個過程基本上是bean本身的逆過程(因此稱爲控制反轉),通過使用類的直接構造或服務定位器模式等機制來控制其依賴項的實例化或位置。

org.springframework.beas 和org.springframework.context包是Spring框架的IoC容器的基礎。BeanFactory接口提供了能夠管理任何類型對象的高級配置機制。ApplicationContext是BeanFactory的子接口。它添加了:

  • 更容易與Spring的AOP特性集成
  • 消息資源處理(用於國際化)
  • 事件發佈
  • 特定於應用程序層的上下文,如用於web應用程序的WebApplicationContext

簡而言之,BeanFactory提供了配置框架和基本功能,而ApplicationContext添加了更多企業特定的功能。ApplicationContext是BeanFactory的一個完整超集,在本章描述Spring的IoC容器時專門使用它。有關使用BeanFactory而不是ApplicationContext的更多信息,請參見The BeanFactory。

在Spring中,構成應用程序主幹並由Spring IoC容器管理的對象稱爲bean。bean是由Spring IoC容器實例化、組裝和管理的對象。否則,bean只是應用程序中的衆多對象之一。bean及其之間的依賴關係反映在容器使用的配置元數據中。

1.2 容器概述

org.springframework.context。ApplicationContext接口表示Spring IoC容器,負責實例化、配置和組裝bean。容器通過讀取配置元數據獲取關於要實例化、配置和組裝哪些對象的指令。配置元數據用XML、Java註解或Java代碼表示。它允許您表達組成應用程序的對象以及這些對象之間豐富的相互依賴關係。

ApplicationContext接口的幾個實現由Spring提供。在獨立應用程序中,通常創建ClassPathXmlApplicationContext或FileSystemXmlApplicationContext的實例。雖然XML一直是定義配置元數據的傳統格式,但是可以通過提供少量XML配置以聲明方式支持這些額外的元數據格式,從而指示容器使用Java註解或代碼作爲元數據格式。

在大多數應用程序場景中,不需要顯式的用戶代碼來實例化一個或多個Spring IoC容器實例。例如,在web應用程序場景中,應用程序的web. XML文件中的8行(或更多)標準web描述符XML通常就足夠了(參考Convenient ApplicationContext Instantiation for Web Applications)。如果您使用Spring Tool Suit(一個eclipse支持的開發環境),那麼只需幾次鼠標單擊或擊鍵,您就可以輕鬆地創建這個樣板配置。

下圖顯示了Spring如何工作的高級視圖。您的應用程序類與配置元數據相結合,這樣,在創建並初始化ApplicationContext之後,您就擁有了一個完全配置和可執行的系統或應用程序。

在這裏插入圖片描述

1.2.1 配置元數據

如上圖所示,Spring IoC容器使用配置元數據的一種形式。此配置元數據表示作爲應用程序開發人員,您如何告訴Spring容器實例化、配置和組裝應用程序中的對象。

配置元數據通常以簡單直觀的XML格式提供,本章的大部分內容都使用這種格式來傳達Spring IoC容器的關鍵概念和特性。

基於xml的元數據不是惟一允許的配置元數據形式。Spring IoC容器本身與實際編寫配置元數據的格式完全解耦。現在,許多開發人員爲他們的Spring應用程序選擇基於java的配置。

有關在Spring容器中使用其他形式的元數據的信息:

  • 基於註解的配置: spring2.5引入了對基於註解的配置元數據的支持。
  • 基於java的配置: 從Spring 3.0開始,Spring JavaConfig項目提供的許多特性成爲Spring核心框架的一部分。因此,您可以使用Java而不是XML文件來定義應用程序類外部的bean。要使用這些新特性,請參見@Configuration、@Bean、@Import和@DependsOn註解。

Spring配置由容器必須管理的至少一個和通常多個bean定義組成。基於xml的配置元數據將這些bean配置爲頂級beans元素中的bean元素。Java配置通常在@Configuration類中使用@ bean註釋的方法。

這些bean定義對應於組成應用程序的實際對象。通常,您要定義服務層對象、數據訪問對象(DAOs)、表示對象(如Struts Action實例)、基礎設施對象(如Hibernate SessionFactories)、JMS隊列等等。通常,不會在容器中配置細粒度域對象,因爲通常由DAOs和業務邏輯負責創建和加載域對象。但是,您可以使用Spring與AspectJ的集成來配置在IoC容器控制之外創建的對象。
參見Using AspectJ to dependency-inject domain objects with Spring。

下面的示例展示了基於xml的配置元數據的基本結構:



<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="..." class="...">  
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions go here -->

</beans>

id屬性是標識單個bean定義的字符串。
class屬性定義bean的類型並使用完全限定的類名。

id屬性的值引用協作對象。本例中沒有顯示用於引用協作對象的XML。更多信息參考 Dependencies。

1.2.2 實例化容器

提供給ApplicationContext構造函數的位置路徑是資源字符串,它允許容器從各種外部資源(如本地文件系統、Java類路徑等)裝載配置元數據。

ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

在瞭解了Spring的IoC容器之後,您可能想了解更多關於Spring的資源抽象(如Resources中所述)的信息,它提供了一種方便的機制,用於從URI語法中定義的位置讀取InputStream。特別是,資源路徑用於構造應用程序上下文,如Application Contexts and Resource Paths中所述。

下面的示例顯示了服務層對象(services.xml)配置文件:



<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- services -->

    <bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
        <property name="accountDao" ref="accountDao"/>
        <property name="itemDao" ref="itemDao"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for services go here -->

</beans>


下面的例子展示了數據訪問對象daos.xml文件:



<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="accountDao"
        class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for data access objects go here -->

</beans>


在前面的示例中,服務層由PetStoreServiceImpl類和JpaAccountDao和JpaItemDao類型的兩個數據訪問對象(基於JPA對象-關係映射標準)組成。屬性名元素引用JavaBean屬性的名稱,而ref元素引用另一個bean定義的名稱。id和ref元素之間的這種鏈接表達了協作對象之間的依賴關係。有關配置對象依賴項的詳細信息,請參閱 Dependencies。

組合基於xml的配置元數據

讓bean定義跨越多個XML文件可能很有用。通常,每個單獨的XML配置文件表示體系結構中的邏輯層或模塊。
可以使用應用程序上下文構造函數從所有這些XML片段加載bean定義。此構造函數接受多個資源位置,如前一節所示。或者,使用一個或多個元素來從另一個或多個文件加載bean定義。下面的例子演示瞭如何做到這一點:



<beans>
    <import resource="services.xml"/>
    <import resource="resources/messageSource.xml"/>
    <import resource="/resources/themeSource.xml"/>

    <bean id="bean1" class="..."/>
    <bean id="bean2" class="..."/>
</beans>


在前面的示例中,外部bean定義是從三個文件加載的:服務。xml, messageSource。xml和themeSource.xml。所有位置路徑都相對於執行導入的定義文件,因此services.xml必須與執行導入的文件位於相同的目錄或類路徑位置,而messageSource.xml和themeSource.xml必須位於導入文件位置之下的資源位置。可以看到,前面的斜槓被忽略了。但是,考慮到這些路徑是相對的,所以最好不要使用斜槓。根據Spring模式,要導入的文件的內容,包括頂級的beans元素,必須是有效的XML bean定義。

在父目錄使用使用相對"…/”路徑引用文件是可能的,但不推薦這樣做。這樣做會在當前應用程序之外的文件上創建一個依賴項。特別地,對於classpath:URLs(例如,classpath:…/services.xml),不推薦使用這個引用,因爲運行時解析過程會選擇“最近的”類路徑根,然後查看它的父目錄。類路徑配置更改可能導致選擇不同的、不正確的目錄。您總是可以使用完全限定的資源位置,而不是相對路徑:例如,file:C:/config/services.xml或classpath:/config/services.xml。但是,請注意,您正在將應用程序的配置耦合到特定的絕對位置。通常更可取的做法是爲這些絕對位置保留一個間接的地址——例如,通過“${…}”佔位符,這些佔位符在運行時根據JVM系統屬性解析。

名稱空間本身提供了import指令特性。除了普通bean定義之外,還有一些配置特性可以在Spring提供的XML名稱空間選擇中找到——例如,context和util名稱空間。

Groovy Bean定義領域特定語言

作爲外部化配置元數據的另一個示例,bean定義也可以用Spring的Groovy bean定義DSL表示,這在Grails框架中是已知的。通常,這樣的配置位於".groovy"文件,其結構如下例所示:

beans {
    dataSource(BasicDataSource) {
        driverClassName = "org.hsqldb.jdbcDriver"
        url = "jdbc:hsqldb:mem:grailsDB"
        username = "sa"
        password = ""
        settings = [mynew:"setting"]
    }
    sessionFactory(SessionFactory) {
        dataSource = dataSource
    }
    myService(MyService) {
        nestedBean = { AnotherBean bean ->
            dataSource = dataSource
        }
    }
}

這種配置風格在很大程度上等同於XML <bean>定義,甚至支持Spring的XML配置名稱空間。它還允許通過importBeans指令導入XML bean定義文件。

1.2.3 使用容器

AplicationContext是能夠維護不同bean及其依賴項的註冊表的高級工廠的接口。通過使用方法T getBean(String name, Class requiredType),可以檢索bean的實例。

ApplicationContext允許您讀取bean定義並訪問它們,如下面的示例所示:

// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// use configured instance
List<String> userList = service.getUsernameList();

對於Groovy配置,引導看起來非常類似。它有一個不同的上下文實現類,它支持groovy(但也理解XML bean定義)。下面的示例顯示了Groovy配置:

ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");

最靈活的變體是GenericApplicationContext與reader委託相結合—例如,XmlBeanDefinitionReader用於XML文件,如下例所示:

GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();

您還可以爲Groovy文件使用GroovyBeanDefinitionReader,如下面的示例所示:

GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();

您可以在相同的ApplicationContext上混合和匹配這樣的reader委託,從不同的配置源讀取bean定義。

然後,您可以使用getBean來檢索bean的實例。ApplicationContext接口有一些用於檢索bean的其他方法,但在理想情況下,應用程序代碼不應該使用它們。

實際上,您的應用程序代碼應該完全不調用getBean()方法,因此完全不依賴於Spring api。例如,Spring與web框架的集成爲各種web框架組件(如控制器和jsf管理的bean)提供了依賴項注入,允許您通過元數據(如自動裝配註釋)聲明對特定bean的依賴項。

1.3 Bean 概述

Spring IoC容器管理一個或多個bean。這些bean是使用您提供給容器的配置元數據創建的(例如,以XML bean定義的形式)。

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

  • 包限定的類名:通常,定義bean的實際實現類。
  • Bean行爲配置元素,它表示Bean在容器中的行爲(範圍、生命週期回調,等等)。
  • 對該bean執行其工作所需的其他bean的引用。這些引用也稱爲協作者或依賴項
  • 要在新創建的對象中設置的其他配置設置—例如,池的大小限制或管理連接池的bean中使用的連接數。

此元數據轉換爲組成每個bean定義的一組屬性。下表描述了這些屬性:

Property Explained in…​
Class Instantiating Beans
Name Naming Beans
Scope Bean Scopes
Constructor arguments Dependency Injection
Properties Dependency Injection
Autowiring mode Autowiring Collaborators
Lazy initialization mode Lazy-initialized Beans
Initialization method Initialization Callbacks
Destruction method Destruction Callbacks

除了包含關於如何創建特定bean的信息的bean定義之外,ApplicationContext實現還允許註冊容器外創建的現有對象(由用戶創建)。這是通過getBeanFactory()方法訪問ApplicationContext的BeanFactory來實現的,該方法返回BeanFactory的DefaultListableBeanFactory實現。DefaultListableBeanFactory通過registerSingleton(…)和registerBeanDefinition(…)方法支持這種註冊。但是,典型的應用程序只使用通過常規bean定義元數據定義的bean。

Bean元數據和手動提供的單例實例需要儘早註冊,以便容器在自動裝配和其他自動檢查步驟期間正確地推斷它們。雖然在某種程度上支持覆蓋現有的元數據和現有的單例實例,但是在運行時註冊新bean(同時對工廠進行實時訪問)並沒有得到官方支持,這可能會導致併發訪問異常、bean容器中的不一致狀態,或者兩者都有。

1.3.1 命名Bean

每個bean都有一個或多個標識符。這些標識符在承載bean的容器中必須是惟一的。一個bean通常只有一個標識符。但是,如果需要多個別名,則可以將額外的別名視爲別名。

在基於xml的配置元數據中,可以使用id屬性、name屬性或兩者都使用來指定bean標識符。id屬性允許您只指定一個id。通常,這些名稱是字母數字(‘myBean’、'someService’等),但它們也可以包含特殊字符。如果希望爲bean引入其他別名,還可以在name屬性中指定它們,中間用逗號(,)、分號(;)或空格分隔。注意,在Spring 3.1之前的版本中,id屬性被定義爲xsd: id類型,這限制了可能的字符。從3.1開始,它被定義爲xsd:string類型。注意,容器仍然強制bean id惟一性,但不再由XML解析器強制。

您不需要爲bean提供名稱或id。如果您沒有顯式地提供名稱或id,則容器將爲該bean生成唯一的名稱。但是,如果希望通過名稱引用該bean,則必須通過使用ref元素或Service Locator樣式查找來提供名稱。不提供名稱的動機與使用inner beans 和autowiring collaborators有關。

約定是在命名bean時使用標準Java約定作爲實例字段名。也就是說,bean名稱以小寫字母開頭,並從那裏開始採用駝峯格式。此類名稱的示例包括accountManager、accountService、userDao、loginController等。

一致地命名bean使您的配置更容易閱讀和理解。另外,如果您使用Spring
AOP,在將建議應用到一組按名稱關聯的bean時,它會有很大幫助。

通過在類路徑中掃描組件,Spring按照前面描述的規則爲未命名的組件生成bean名稱:本質上,使用簡單的類名並將其初始字符轉換爲小寫。但是,在(不尋常的)特殊情況下,如果有多個字符,並且第一個和第二個字符都是大寫的,則保留原來的大小寫。這些規則與java.beans.Introspector.decapitalize (Spring在這裏使用)定義的規則相同。

在Bean定義之外別名化Bean

在bean定義本身中,通過使用id屬性指定的最多一個名稱和name屬性中任意數量的其他名稱的組合,可以爲bean提供多個名稱。這些名稱可以是相同bean的別名,在某些情況下非常有用,比如通過使用特定於該組件本身的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-configuration
如果使用Javaconfiguration,則可以使用@Bean註釋來提供別名。有關詳細信息,請參見 Using the @Bean Annotation。

1.3.2 實例化bean

bean定義本質上是創建一個或多個對象的方法。當被請求時,容器查看命名bean的配方,並使用該bean定義封裝的配置元數據來創建(或獲取)一個實際對象。

如果使用基於xml的配置元數據,則指定要在bean元素的class屬性中實例化的對象的類型(或類)。這個class屬性(在內部是BeanDefinition實例上的一個類屬性)通常是強制性的。(有關異常,請參見Instantiation by Using an Instance Factory Method 和Bean Definition Inheritance。)你可以通過以下兩種方式之一使用Class屬性:

  • 通常,在容器本身通過反射調用其構造函數直接創建bean的情況下,指定要構造的bean類,這有點類似於使用new操作符的Java代碼.
  • 指定包含用於創建對象的靜態工廠方法的實際類,在不太常見的情況下,容器調用類上的靜態工廠方法來創建bean。從靜態工廠方法調用返回的對象類型可以是同一個類,也可以完全是另一個類。

如果希望爲靜態嵌套類配置bean定義,則必須使用嵌套類的二進制名。
例如,如果您在com.example包中有一個類名爲SomeThing。這個類有一個靜態的嵌套類叫做OtherThing,bean定義上的class屬性的值是com.example.SomeThing$OtherThing
注意,在名稱中使用$字符將嵌套的類名與外部類名分隔開。

用構造函數實例化

當您通過構造函數方法創建一個bean時,所有的普通類都可以被Spring使用並與Spring兼容。也就是說,正在開發的類不需要實現任何特定的接口,也不需要以特定的方式進行編碼。只需指定bean類就足夠了。但是,根據具體bean使用的IoC類型,可能需要一個默認(空)構造函數

Spring IoC容器幾乎可以管理您希望它管理的任何類。它不僅限於管理真正的javabean。大多數Spring用戶更喜歡實際的javabean,它只有一個默認的(無參數的)構造函數,以及根據容器中的屬性建模的適當的setter和getter方法。您還可以在容器中包含更多非bean風格的類。例如,如果您需要使用完全不符合JavaBean規範的遺留連接池,Spring也可以管理它。

使用基於xml的配置元數據,您可以按如下方式指定bean類:

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

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

有關向構造函數提供參數(如果需要)和在構造對象後設置對象實例屬性的機制的詳細信息,請參閱Injecting Dependencies.

用靜態工廠方法實例化

在定義使用靜態工廠方法創建的bean時,使用class屬性指定包含靜態工廠方法的類,使用factory-method屬性指定工廠方法本身的名稱。您應該能夠調用這個方法(帶有可選參數,如後面所述)並返回一個活動對象,該對象隨後被視爲是通過構造函數創建的。這種bean定義的一個用途是在遺留代碼中調用靜態工廠。

下面的bean定義指定通過調用工廠方法來創建bean。定義不指定返回對象的類型(類),只指定包含工廠方法的類。在本例中,createInstance()方法必須是一個靜態方法。下面的例子演示瞭如何指定工廠方法:

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

下面的例子展示了一個可以使用前面的bean定義的類

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

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

有關向工廠方法提供(可選)參數和在對象從工廠返回後設置對象實例屬性的機制的詳細信息,see 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;
    }
}

一個工廠類也可以包含多個工廠方法,如下例所示:



<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

<bean id="accountService"
    factory-bean="serviceLocator"
    factory-method="createAccountServiceInstance"/>


The following example shows the corresponding class:

public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    private static AccountService accountService = new AccountServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }

    public AccountService createAccountServiceInstance() {
        return accountService;
    }
}

這種方法表明,可以通過依賴項注入(DI)來管理和配置工廠bean本身。See Dependencies and Configuration in Detail.

在Spring文檔中,“factory bean”指的是在Spring容器中配置的bean,它通過instance 或 static 工廠方法創建對象。相反,FactoryBean(注意大小寫)指的是特定於spring的FactoryBean。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章