Spring Framework 官方文檔中文版—Core_part_1

內容過長,core部分分開發布,core章節第二部分點擊:Spring Framework 官方文檔中文版—Core_part_2
主目錄或專題地址可以點擊:主目錄, 專題地址

朋友的博客, 內容非常贊??, 想學更多的Java技術, 可以去他那看看
優秀的Java干貨博客

此部分的參考文檔,包含了Spring Framework的所有絕對重點內容。

這節最重要的內容就是Spring Framework的控制反轉(IoC)容器。徹底了解IoC容器之后,緊接著會對AOP技術進行全面的講解。Spring Framework擁有自己的AOP框架,其在概念上很容易理解,并且成功解決了Java企業級編程中80%的AOP需求。

同時也介紹了Spring與AspectJ(最具特色,也是Java企業級應用中最成熟的AOP實現)的集成。

IoC容器

Spring IoC容器和bean的介紹

這節內容主要介紹Spring Framework實現IoC的原理。IoC也被叫做依賴注入(DI)。這是一個對象定義他們的依賴的過程。其他對象使用它時,只能通過構造方法參數,傳遞給工廠方法的參數,或者被設置到對象的屬性上并在此對象實例化后被構建或者從一個工廠方法返回。容器在創建bean時注入這些依賴。這個過程是從根本上看是完全相反的,所以叫做控制反轉(IoC)。

此處硬生翻譯原文,估計會造成誤解,原文如下:This chapter covers the Spring Framework implementation of the Inversion of Control (IoC) [1] principle. IoC is also known as dependency injection (DI). It is a process whereby objects define their dependencies, that is, the other objects they work with, only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse, hence the name Inversion of Control (IoC), of the bean itself controlling the instantiation or location of its dependencies by using direct construction of classes, or a mechanism such as the Service Locator pattern.

org.springframework.beansorg.springframework.context兩個包是Spring Framework的IoC容器基礎包。BeanFactory接口提供了高級的配置機制,可以管理任何類型的對象。ApplicationContextBeanFactory的子接口,它在BeanFactory的基礎上增加了更容易與Spring AOP集成的功能,語言資源處理(用于國際化),發布事件,和程序應用層的特定上下文(例如web應用中的WebApplicationContext)。

總之,BeanFactory提供了配置框架和基本功能。ApplicationContext提供更多企業級應用的相關功能。ApplicationContext是BeanFactory完整的超集,專門用于此章節。如果想使用BeanFactory來替代ApplicationContext,可以參考后面專門介紹BeanFactory的章節。

在Spring中,構成你的應用程序的主干對象(譯者注:例如發送郵件的對象),是被SpringIoC容器來管理的,它們被稱為bean。bean是一個對象,它被Spring IoC容器來實例化,組裝和管理。bean只是應用程序中許多對象之一。bean和bean之間的依賴關系則反映在容器使用的配置中。

容器概述

org.springframework.context.ApplicationContext接口代表了Spring IoC容器,它負責實例化,組裝和配置上面所說的bean。容器通過讀取配置來實例化,組裝和配置bean。配置的主要形式有XML,Java注解和Java代碼,它允許你來定義應用中的對象和他們之間復雜的依賴關系。

ApplicationContext接口的一些實現做到了開箱即用。在單機應用中通常會創建一個ClassPathXmlApplicationContext>FileSystemXmlApplicationContext的實例。

大多數應用場景中,不需要用戶顯示的創建一個或多個Spring IoC容器的實例。例如,一個web應用,簡單的8行(大約)代碼的web.xml文件即可滿足需求。(后面章節會給出在web應用中如何方便的創建ApplicationContext實例)。如果你正在使用基于eclipse的Spring Tool Suite,那么這個配置將會很容易創建,只需要為數不多的鍵盤鼠標操作。

下圖為Spring如何工作的抽象視圖。應用中的類被配置文件整合起來了,所以在ApplicationContext被創建和初始化后,你已經有一個完全配置和可執行的系統或應用了。

image
配置元數據

如上圖展示的,Spring IoC容器使用了配置;作為一個應用開發者,這些配置可以讓你告訴Spring容器去實例化,配置,組裝應用程序中的對象。

配置元數據傳統上是使用簡單,直觀的XML文件,這節中,主要用XML配置來說明Spring IoC容器的關鍵概念和特性。

注意:基于XML并不是配置的唯一途徑。Spring IoC容器與配置文件完全解耦。現在,許多開發者在他們的Spring應用中選擇使用基于Java的配置(后續章節會介紹此部分內容)。

關于配置Spring容器的其他形式信息,可以參考:

  • 基于注解配置:Spring 2.5引入的基于注解配置的元數據。

  • 基于Java配置:Spring 3.0開始,Spring JavaConfig提供的許多特性已經成為了Spring Framework的核心。你可以使用Java而不是XML文件來定義程序類外部的bean。要使用這些新特性,可以參考@Configuration,@Bean,@Import和@DependsOn等注解。

Spring的配置由至少一個,通常多個Spring容器管理的bean定義組成。基于XML來配置bean,是配置在<bean/>元素內,其父元素,也是頂級元素為<beans/>。基于Java配置通常用@Bean來標注方法或用@Configuration來標注類。

這些bean定義對應于構成應用程序的實際對象。通常你定義服務層對象,數據訪問層對象(DAO),展示層對象例如Struts Action實例,基礎底層對象例如Hibernate的SessionFactoriesJMS Queues等等。通常,不會在容器中配置細粒度的domain對象(例如數據庫po對象),因為加載這些類通常是DAO和業務邏輯代碼的職責。然而,你可以利用Spring集成的AspectJ來在Spring IoC容器之外創建和加載domain對象。這部分內容在后面相關章節會給出介紹。

下面的例子基于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
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="..." class="...">
        <!-- 在這里定義bean和它相關的內容 -->
    </bean>

    <bean id="..." class="...">
        <!-- 在這里定義bean和它相關的內容 -->
    </bean>

    <!-- 更多的bean定義... -->

</beans>

屬性id是一個字符串,是某個bean的唯一標識。class屬性用來定義bean的類型,這里需要使用類的完全限定名。id屬性的值可以被協作的bean來引用。關于協作bean的配置,這個例子并沒有給出,但是可以參考下面Dependencies章節。

實例化一個容器對象

實例化一個Spring IoC容器非常簡單。傳遞給ApplicationContext構造方法的相對路徑或絕對路徑,實際上是資源的路徑,它允許容器從外部各種資源(例如本地文件系統,Java類路徑等等)加載配置元數據。

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

學習了Spring IoC容器之后,你可能會想去知道更多有關于Spring的Resource,像在Resource章節描述的一樣,Spring Resource提供了方便的機制來使用URI規則讀取InputStream。尤其是,Resource路徑被用來構建應用程序context,下面章節會給出具體描述。

下面的例子展示了service層對象(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
        http://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>

下面的例子給出了數據處理層對象的配置:

<?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
        http://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>

上面的例子,service層由PetStoreServiceImpl和兩個JpaAccountDaoJpaItemDao類型的數據處理對象(基于JPA標準)組成。property標簽的name屬性的值意為Java Bean屬性的名稱,ref屬性值則為引用的另一個定義好的bean。idref的關聯,表達了對象之間的關系和相互引用等。對象之間詳細的依賴關系介紹,可以參考后面的Dependencies章節。

編寫基于XML的配置文件

通過多個XML配置文件來定義bean是非常有必要的。通常,在你的架構中,每一個XML配置文件表示特定的邏輯層或模塊。

你可以使用application context構造方法來從所有的XML來加載bean。這個構造方法可以接受多個資源,如上一節所示。還有一種選擇,使用<import/>標簽來加載其他的XML配置文件,如下面例子:

<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文件,services.xmlmessageSource.xml,和themeSource.xml。所有的路徑都是以下幾種,此文件的相對路徑,所以services.xml必須是同一目錄下,或者是類路徑,例如messageSource.xmlthemeSource.xml。正如你看到的一個主要的斜線被忽略了,這因為路徑是相對的,所以最好不要使用斜線。被導入文件的內容,包括最頂級標簽<beans/>,必須是符合Spring標準的有效XML bean定義。

引用父目錄文件的時候,使用相對路徑“../”,這樣做是可以的,但是并不推薦這樣做。這樣做會創建一個相對當前應用程序之外的文件的依賴關系。尤其是,這樣引用不推薦用于"classpath:"路徑(例如"classpath:../services.xml"),在運行時解析過程中會選擇“最近的”類路徑的根目錄,然后再尋找它的父目錄。classPath配置的改變,可能會導致選擇不同的、錯誤的目錄。

你也可以使用絕對路徑來代替相對路徑:例如“file:C:/config/services.xml” 或 “classpath:/config/services.xml”。然而,你可能意識到,這樣做會將你的應用程序的配置,耦合到一個特定的位置上。一般不會直接直接配置這種絕對路徑,例如,可以使用"${…}"占位符來獲得JVM運行時的系統屬性。

import標簽是beans命名空間本身提供的特性。在Spring提供的XML名稱空間功能里,除了簡單的bean定義之外,還有更多的配置特性,例如“context”和“util”名稱空間等。

Groovy利用DSL來定義Bean

作為更進一步展示配置元數據,定義bean也可以用Spring的Groovybean定義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指令來導入其他的XMLbean配置文件。

使用容器

接口ApplicationContext是一個高級工廠的接口,它能夠維護不同bean及其依賴項的注冊表。使用方法getBean(String name, Class<T> requiredType)可以讓你檢索到bean的實例。

接口ApplicationContext可以讓你讀取bean的定義并訪問他們,如下所示:

// 創建ApplicationContext實例,加載bean配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

// 從上述配置文件中配置的bean中,檢索你所需要的bean
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// 使用配置好的實例,這就是具體的業務代碼了
List<String> userList = service.getUsernameList();

使用groovy配置的時候,看起來與上面非常類型,只不過換成ApplicationContext的其他實現類:

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

最靈活的方式是使用GenericApplicationContext,它整合了具有代表性的幾種reader,例如為XML準備的XmlBeanDefinitionReader:

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

這樣reader就可以被整合,匹配到同一個ApplicationContext中來讀取多種類型的配置。

然后你可以通過getBean來檢索你的bean的實例了。ApplicationContext接口也有一些其他的方法來檢索bean,但是你的代碼中不太可能用到。實際上,你的代碼里連getBean()都不需要調用,所以也就不需要依賴于Spring的API。例如,Spring與web框架的集成,為各種web框架組件(例如controller,JSF)提供了依賴注入,允許您通過配置聲明對特定bean的依賴(例如:autowiring注解)。

bean概述

一個Spring IoC容器管理一個或多個bean。這些bean是根據你提供給容器的配置來被容器創建的,例如在XML配置中,<bean/>標簽下的配置。

在容器內部,這些bean被表示為BeanDefinition對象,它包含(以及其他信息)了如下的元數據信息:

  • 一個限制包類名:通常是定義好的bean的實現類。

  • bean行為配置元素,規定了bean的容器中的行為方式(范圍,生命周期回調等等)。

  • 對其他bean的引用;這些引用也被叫做協作者和依賴。

  • 在新創建的對象中設置其他的配置項,例如,管理連接池的bean,你可以設置這個bean的可以使用的連接數,或者最大,最小連接數。

這些元數據轉換為組成每個bean定義的一組屬性。

下面為具體定義bean的一些具體屬性:

Property 對應的章節名
class 實例化bean
name 命名bean
scope bean應用范圍
構造方法傳參 依賴注入
properties 依賴注入
自動模式 自動裝配依賴
懶加載模式 懶加載bean
初始化方法 初始化回調
銷毀方法 銷毀后回調

除了包含創建bean的特定信息以外,ApplicationContext的實現類還允許用戶在容器外創建現有對象。這是通過調用ApplicationContext的getBeanFactory()方法來實現,這個方法會返回BeanFactory的實現類DefaultListableBeanFactory。它通過registerSingleton(..)registerBeanDefinition(..)方法來支持這。但是,一般的應用只通過配置來定義bean。

通過配置和手動創建單例時,需要今早的進行實例化,這是為了讓容器在自動裝配和其他內省步驟里正確的解析它們。雖然在某種程度上支持現有的元數據和現有的單例實例,但是在運行時(與工廠的實時訪問同時)注冊新bean并沒有得到官方的支持,并且可能導致在bean容器中并發訪問異常和/或不一致的狀態。

命名bean

每一個bean都有一個或多個標識。這些bean的標示符在容器內部都必須是唯一的。bean一般只有一個標示符,但是如果需要多個,其他的可以被認為是別名。

基于XML配置中,你使用idname屬性來指定bean的標識。id屬性允許你指定一個唯一id。按照慣例它的命名是以英文數字組成的(“myBean”, “fooService”等),但是包含特殊字符也是可以的。如果你想給bean增加其他別名,那么可以使用name屬性,以“,”或“;”分割或空格。作為一個歷史記錄,在spring3.1之前的版本中,id屬性被定義為xsd: id類型,它可能限制了一些字符。截止到3.1,它被定義成xsd:string類型。要注意bean的id屬性值在容器中仍要要是唯一的,盡管不再使用XML解析器。

你不需要給bean設置一個id或name。如果沒有設置name和id,容器會給這個bean生成一個唯一的名稱。然而你想通過名稱來引用一個bean,你必須要提供一個名稱來使用ref標簽或服務定位來查找。

bean命名約定
該約定是在命名bean時使用標準Java規范,例如字段名。主要是,bean的命名是以小寫字母開頭,之后使用駝峰是命名。舉例說明:"accountManager","accountService","userDao","loginController"等。

bean的命名應該是讓你的配置更簡單易懂,在使用Spring AOP時采用這里的建議會對你有很大的幫助。

在classPath掃描組件時,Spring會給未命名的bean生成名稱,遵循上面說的規則:實質上就是取簡單類型并將首字母小寫。然而在特殊情況下,類名的前兩個字符都是大寫的話,Spring會采用原始的命名,不會做改動,具體的邏輯可以參考Spring命名的方法java.beans.Introspector.decapitalize

bean的別名設置

在bean的定義本身,你可以給bean提供多個名稱,即利用id屬性提供一個唯一id或利用name屬性提供一個或多個名稱。這些名稱都是一個bean的等價名稱,這在某些情況下是比較有用的,例如應用程序中的多個組件在引用同一個bean的時候,可以使用每個組件他們特定的bean名稱。

然而,在bean的定義處指定別名并不總是足夠的。有時候需要為在其他地方定義的bean引入別名。這樣的案例經常出現在大規模系統中,每個子系統直接都可能有配置,每個子系統都有自己的一組對象定義。基于XML的配置中,你可以使用<alias/>標簽來實現這個。

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

這個例子中,在同意容器內這個bean叫做“fromName”,也可以在用alias命名后被稱為“toName”。

例如,子系統A的配置文件可能去通過名稱subsystemA-dataSource來引用一個DataSource。子系統B的配置想通過名稱subsystemB-dataSource來引用一個DataSource``。當子系統A、B組合到一起時,主系統會用myApp-dataSource```來命名。如果想讓同一對象擁有這三個名字,你可以在主系統中做如下配置

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

現在,每一個子系統和主系統都可以通過唯一的名稱來引用```DataSource``了,并且保證和其他的bean定義沒有沖突,其實它們引用的是同一個bean。

基于Java配置
如果你在使用基于Java的配置,可以用```@Bean```注解來設置別名,后面會有基于Java配置的章節,會在那里詳細說明。
實例化bean

bean的配置,實質上是為了創建一個或多個對象。容器在需要的時候,會查看bean是如何配置的,然后通過這個bean的配置去實例化一個對象。

如果你是基于XML來配置bean的,你可以在<bean/>class屬性中指定對象的類型(或者說是class)。這個class屬性值,實際是BeanDefinition實現類的一個屬性,通常是必填項。有兩種使用Class屬性的方式:

  • 作為代表性的,如果通過容器本身直接實例化bean,指定要構建的bean的類,通過反射調用類的構造方法,這等同于Java的new操作。

  • 指定的實際類,含有一個靜態的工廠方法,調用這個工廠方法后可以創建對象實例,更少的時候,容器在類上調用靜態工廠方法去實例化bean。從調用靜態工廠方法返回的對象類型可以是相同的類或其他類。

內部類名稱
如果你想為靜態內部類配置bean定義,你需要使用內部類的另一種名稱。

例如,如果你在com.example包下有一個Foo類,Foo有一個靜態內部類叫做Bar,那么在定義bean的時候,class屬性的值就應該寫作:

com.example.Foo$Bar

要注意要用"$"將類和內部類隔開。
使用構造方法實例化

當你使用構造方法來創建bean,所有的正常類都可以被Spring來兼容和使用。也就是說,這個類不需要去實現一個指定的接口,也不需要使用指定的某種方式去編寫。簡單的指明bean的class就足夠了。然后,取決于你使用哪種類的IoC容器來實現bean,你可能需要一個默認的(空的)構造方法。

Spring IoC容器幾乎可以管理任何你想管理的class。它并不局限于管理真正的javaBean。大部分使用Spring的使用者,更喜歡用默認的(空的)構造方法,或是class屬性來構建bean。你也可以在容器中使用有意思的非bean風格(non-bean-style)的class。例如,如果你需要使用一個完全不符合JavaBean規范的從前遺留下來的連接池,對此,Spring也一樣可以很好的管理。

基于XML的配置,你可以指定你的bean像下面這樣:

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

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

關于如何向有參構造方法傳遞參數(如果需要),以及在對象實例化后對象屬性如何設置,可以參開依賴注入章節。

使用靜態工廠方法實例化

當你使用了靜態工廠方法來定義一個bean,你使用class屬性來指定的類需要包含靜態工廠方法,再利用factory-method屬性來指定這個靜態工廠方法的名稱。你可以調用此方法并返回一個對象實例。

下面的的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;
    }
}

關于給工程方法傳遞參數,工廠方法返回對象后給對象實例屬性賦值的詳細說明,參考后面章節:依賴和詳細配置。

使用工廠方法實例來實例化

類似于上節中的利用靜態工廠方法實例化,這里指的是利用一個已經存在的bean,調用它的非靜態的工廠方法來創建bean。使用這種機制,class屬性是允許為空的,在factory-bean屬性中,指定當前容器(或父容器)中包含的bean的名稱,這個bean包含調用之后可以創建對象的方法。然后在factory-method屬性中來指定這個方法的名稱:

<!-- 工廠bean, 包含了叫createInstance()的方法 -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- 注入這個bean需要的一些配置項 -->
</bean>

<!-- 這個bean通過上面的工廠bean來創建 -->
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

對應的Java代碼:

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"/>

相關Java代碼:

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

這種方法表明,工廠bean本身可以通過依賴注入(DI)來管理和配置。參考后面章節:依賴和詳細配置。

在Spring文檔中,工廠bean指的是在Spring容器中配置好的bean,它將通過一個實例工廠或靜態工廠來創建對象。與之相比,FactoryBean(注意這個是一個類名)是指Spring中的一個接口,請不要混淆!

依賴

一個典型的企業級應用并不是由相互獨立的對象組成(按照Spring的說法就是bean)。即使是最簡單的應用程序,也是需要有一些對象相互協作才能將整和到一起的應用呈現給終端用戶。

依賴注入(DI)

依賴注入是對象定義他們依賴的過程,這些依賴指的是與之一起協作的其他對象,只通過構造方法參數,工廠方法的參數或對象屬性(調用構造方法或工廠方法后得到的對象)。容器在創建bean之后注入它們的依賴。這個過程是從根本上反轉過來了,因此叫做控制反轉(IoC),bean自己控制實例化或定位它的依賴。

在使用DI機制時,代碼更簡潔,當對象被提供給其依賴關系時,解耦更有效。對象并不去尋找它的依賴,也不知道依賴的位置和class。同樣的,你的class會更容易的去測試,特別是當依賴是接口或抽象類時,可以用它們的子類或實現類來實現單元測試。

DI主要存在兩種方式:基于構造方法的依賴注入,基于setter的依賴注入。

基于構造方法依賴注入

基于構造方法的依賴注入是很成熟的,容器回去調用帶有一定數量參數的構造方法,而其中的每一個參數,則代表了一個依賴。調用一個帶有特定參數的靜態工廠方法與此是幾乎等效的,這里討論的是將參數用于構造函數,并以類似的方式處理靜態工廠方法。下面的例子展示了一個class只能通過用構造方法來進行依賴注入。要注意的是這個類沒有任何特別的地方,它只是一個POJO,不依賴與容器的特定接口,基類或注解等。

public class SimpleMovieLister {

    // SimpleMovieLister有一個依賴是MovieFinder,簡單說就是有個MovieFinder類型的屬性
    private MovieFinder movieFinder;

    // 有了這個構造方法,就可以將movieFinder傳進來并賦值給屬性movieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // 一些其他的業務邏輯,這里忽略. . .
}

處理構造方法參數
處理構造方法參數的時候,要注意匹配好參數的類型。bean定義中,如果構造方法參數沒有潛在的歧義,那么在bean定義中,定義構造函數參數的順序是在bean被實例化時,這些參數被提供給構造函數的順序。考慮一下下面的類:

package x.y;

public class Foo {

    public Foo(Bar bar, Baz baz) {
        // ...
    }
}

不存在潛在的歧義,假設BarBaz沒有繼承關系。因此下面的配置是沒有問題的,你不必在<constructor-arg/>指定構造方法參數的indextype屬性。

<beans>
    <bean id="foo" class="x.y.Foo">
        <constructor-arg ref="bar"/>
        <constructor-arg ref="baz"/>
    </bean>

    <bean id="bar" class="x.y.Bar"/>

    <bean id="baz" class="x.y.Baz"/>
</beans>

當引用另一個bean時,就會知道該類型,并可以自動匹配(就像上面的示例一樣)。當使用一個簡單類型,例如true,Spring并不能判定值的類型,所以不能完成自動匹配。參考下面的類:

package examples;

public class ExampleBean {

    // Number of years to calculate the Ultimate Answer
    private int years;

    // The Answer to Life, the Universe, and Everything
    private String ultimateAnswer;

    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

構造方法參數類型匹配
前面的例子中,如果利用type指定了構造方法參數的類型,那么容器是可以利用類型來自動匹配參數的。例如:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

構造方法參數下標
使用index屬性去明確指定構造方法參數的順序,例如:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>

除了解決多個值的歧義之外,指定索引還可以解決構造函數具有相同類型的兩個參數的問題。要注意,index的值是從0開始的。

構造方法參數名稱
你也可以利用構造方法參數的名稱來消除歧義:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>

記住,要使這正常使用,你的代碼必須使用debug級別來編譯,這樣Spring才可以查找到構造方法的名稱(譯者注:關于利用debug來編譯,此處是一個重要的細節,如果不了解,在今后的工作中,你可能會遇到一些莫名其妙的問題,詳情請點擊<font style=text-decoration:underline color=#548e2e>知識擴展</font>)。如果你不能利用debug級別編譯程序(或者說你不想),你可以使用@ConstructorProperties注解去設置好你的構造方法名稱。下面是一個簡單的例子:

package examples;

public class ExampleBean {

    // Fields omitted

    @ConstructorProperties({"years", "ultimateAnswer"})
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}
基于setter依賴注入

容器調用無參構造方法或沒有參數的靜態工廠方法后,會得到bean的實例,基于setter的依賴注入則是在得到bean的實例后,容器調用bean實例的setter方法來注入依賴屬性。

下面的例子展示了只能通過setter方法依賴注入的類。這個類是非常常見的Java類。這是一個和容器特定接口沒用依賴的,沒用注解也沒有基類的POJO。

public class SimpleMovieLister {

    // 有一個the MovieFinder的屬性,也就是依賴
    private MovieFinder movieFinder;

    // 有一個setter方法,所以Spring容器可以調用這歌方法來注入一個MovieFinder
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // 其他邏輯則省略了...
}

你應該記得前面內容中介紹的ApplicationContext,它為它所管理的bean提供了基于構造方法和setter的依賴注入。在通過構造函數方法注入一些依賴項之后,它還支持基于setter方式的依賴注入。你以BeanDefinition的形式來配置依賴項,可以將其與PropertyEditor實例結合使用,以將屬性從一種格式轉換為另一種格式。然而許多Spring的使用者并不會直接用這些類,而是使用XML配置bean定義,帶有注解的組件(例如用@Component@Controller注解標注的類),或者帶有@Configuration注解的類中帶有@bean注解的方法。這些形式的配置最終都會被轉換到BeanDefinition實例的內部,并被Spring IoC容器來加載實例。

選擇基于構造方法還是setter的依賴注入?
你可以混合使用基于構造方法的和setter的依賴注入,利用構造方法的方式來強制依賴,利用setter的方式來做可選的依賴,這些方式是很不出錯的。注意,在setter方法上使用@Required注解來標注,可以讓此屬性變為必須注入的。

Spring團隊通常更贊成使用構造方法的方式依賴注入。因為它支持將應用程序組件以作為不可變對象來實現,并確保所需的依賴項不是null。此外,依靠構造方法注入的對象會在完全初始化后返回。另外,擁有大量的構造方法是一個非常bad的代碼,這意味著這個類承載的太多的功能,需要重構了。

基于Setter的依賴注入應該主要用于可選的依賴項,可以用來給對象的一些屬性設置默認值。否則,必須在代碼使用依賴項的地方進行非空檢查。setter注入的一個好處就是setter方法可以讓對象在后面可以進行二次配置或重新注入。```JMX```管理bean是利用setter注入的一個非常好的例子。

在處理一個第三方類并且這個類沒有源代碼時。這個第三方類沒有暴露任何setter方法,那么依賴注入的唯一途徑就是通過構造方法。
依賴的解析過程

如下是容器解析bean依賴的過程:

  • ApplicationContext是通過配置元數據來創建和初始化的,這些元數據描述了所有的bean。配置元信息可以通過XML,Java代碼或注解來指定。

  • 對于每個bean,它的依賴用屬性,構造方法參數,或者靜態工廠方法參數的形式來表達。bean被創建好之后這些依賴會被提供給它。

  • 每一個屬性或構造方法參數都是要設置的值的實際定義,或者對容器內另一個bean的引用。

  • 每一個屬性或構造方法參數所指定的值,都將被轉換為其實際類型的值。默認情況下,Spring可以將以字符串格式提供的值轉換為所有內置類型,如int,long,String,boolean等等。

容器在創建的時,Spring容器會驗證每一個bean的配置。然而,在實際創建bean之前,bean的屬性本身不會被設置。單例的和被設置為首先加載(pre-instantiated)的bean會在容器初始化后被創建。bean的范圍在下一章給出詳細介紹。除此之外,bean只會在需要它的時候被創建。創建bean的過程可能會引起一些列的bean被創建,例如bean的依賴、其依賴的依賴等等會被一起創建和分配。注意,在后面,依賴之間解析不匹配可能會顯現出來,即首先創建有影響的bean時候。

循環依賴

如果你主要使用構造方法的方式注入,有可能造成無法解決的循環依賴。

例如,class A需要通過構造方法注入一個Class B的實例,classB同樣需要通過構造方法注入class A的實例。如果你為class A和class B配置bean并且互相注入,Spring IoC容器在運行時會發現這是循環引用,然后拋出異常:BeanCurrentlyInCreationException。

一個解決途徑就是去編輯編代碼,讓這些類可以通過setter注入。作為另一種選擇,避免使用構造方法注入而只使用setter注入。換句話說,盡管這并推薦存在循環依賴,但是你可以使用setter來配置循環依賴。

與一般的情況(沒有循環依賴)不同,bean A和bean B之間的循環依賴關系迫使其中一個bean在被完全初始化之前注入另一個bean(典型的雞生蛋,蛋生雞場景)。

通常你可以信任Spring來做正確的事情。它可以發現配置問題,例如容器加載時發現引用不存在的bean,循環依賴等。bean在被實際創建后,Spring會盡可能晚的設置屬性和解決依賴。這意味著Spring容器正常加載后會晚一些拋出異常,也就是說只有當你開始使用這個對象時,創建這個對象或他們的依賴時產生了異常。例如,因為缺失類屬性或無效的屬性值,bean會拋出一個異常。這可能會導致一些配置問題延遲出現,這就是為什么ApplicationContext是默認先將實例化單例的bean的。在bean被實際使用之前,需要提前花費一些實際和內存,在創建ApplicationContext時發現配置問題,而不是以后來做這些事兒。你可以修改默認的此行為,以便使單例的bean懶加載,而不會預先實例化。

如果沒有循環依賴的存在,當一個或多個協作bean(其他bean的依賴)被注入到一個bean中時,每個協作bean在被注入到bean之前完全被配置。這意味著如果bean A依賴于bean B,Spring IoC容器在調用bean A 上的setter方法前將完全配置好bean B。換句話說,bean被實例化了(如果不是一個預先實例化的單例),他的依賴被設置了,相關的生命周期方法被調用了。

依賴注入的例子

下面的例子是用XML來配置的基于setter的依賴注入:

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter注入,并且嵌套引用了另一個bean -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter注入,引用其他bean的方式看起來更加整潔 -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

相關Java代碼:

public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

    public void setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }

    public void setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }

    public void setIntegerProperty(int i) {
        this.i = i;
    }
}

上面例子中,聲明的setter與再XML中指定的類型相匹配。下面的例子使用了基于構造方法的依賴注入(constructor-based):

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- 構造方法注入另一個bean -->
    <constructor-arg>
        <ref bean="anotherExampleBean"/>
    </constructor-arg>

    <!-- 構造方法注入,這樣引用比上面更簡潔 -->
    <constructor-arg ref="yetAnotherBean"/>

    <constructor-arg type="int" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

相關Java代碼:

public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

    public ExampleBean(
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        this.beanOne = anotherBean;
        this.beanTwo = yetAnotherBean;
        this.i = i;
    }
}

bean定義中指定的構造方法參數,會被ExampleBean構造方法的參數來使用。

現在來考慮一下這個例子的變體,作為可以替代構造方法的方式,Spring在之前告訴了你,可以使用靜態工廠方法來返回一個bean的實例:

<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
    <constructor-arg ref="anotherExampleBean"/>
    <constructor-arg ref="yetAnotherBean"/>
    <constructor-arg value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

相關Java代碼:

public class ExampleBean {

    // 私有的構造方法
    private ExampleBean(...) {
        ...
    }

    // 一個靜態工廠方法; 此方法的參數可以認為就是xml配置中引用的其他bean
    public static ExampleBean createInstance (
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {

        ExampleBean eb = new ExampleBean (...);
        // 其他操作省略...
        return eb;
    }
}

靜態工廠方法的參數通過<constructor-arg />標簽來提供,這點與基于構造方法的注入恰好相同。工廠方法返回的class類型不一定要與其所在的類的類型相同,雖然在本例子中恰好是一樣的。實例工廠方法基本是以相同的方式來使用(除了使用factory-bean屬性來替代class屬性),這里不再給出詳細說明。

依賴和配置的細節

就像前面章節提到的,你可以定義bean屬性,構造方法參數作為其他bean的引用,Spring的基于XML配置支持子標簽<property/><constructor-arg/>等支持這個功能。

Straight values (基本類型, String等等)

標簽<property/>value屬性值,以可讀的字符串形式指定了屬性或構造方法參數。Spring的轉換服務(即conversion service,后面章節會介紹)就是用來將這些屬性或參數的值從字符串轉換為它們的實際類型。

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- results in a setDriverClassName(String) call -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="masterkaoli"/>
</bean>

下面例子使用了更簡潔的XML配置:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close"
        p:driverClassName="com.mysql.jdbc.Driver"
        p:url="jdbc:mysql://localhost:3306/mydb"
        p:username="root"
        p:password="masterkaoli"/>

</beans>

前面的XML配置更加簡潔。然而,類似打字錯誤這樣的問題是在運行時被發現而不是在設計時,除非你使用的IDE是像<font style=text-decoration:underline color=#548e2e>IntelliJ IDEA</font><font style=text-decoration:underline color=#548e2e>Spring Tool Suite</font>(STS)這樣的,在你定義bean的時候可以自動填寫屬性值。這樣的IDE支持是強烈推薦的。

你也可以向下面這樣配置一個java.util.Properties實例:

<bean id="mappings"
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

    <!-- typed as a java.util.Properties -->
    <property name="properties">
        <value>
            jdbc.driver.className=com.mysql.jdbc.Driver
            jdbc.url=jdbc:mysql://localhost:3306/mydb
        </value>
    </property>
</bean>

Spring容器使用JavaBeans PropertyEditor的機制,將<value/>標簽內的值轉換到java.util.Properties實例中。這是一個非常好的簡寫方式,使用嵌套的<value/>標簽而不是value屬性,也是Spring團隊支持的幾個做法之一。

idref標簽
標簽idref是在容器中將另一個bean的id傳遞到<constructor-arg/><property/>標簽中簡單的辦法,同時也有著簡單的錯誤檢驗功能。

<bean id="theTargetBean" class="..."/>

<bean id="theClientBean" class="...">
    <property name="targetName">
        <idref bean="theTargetBean"/>
    </property>
</bean>

上面的bean定義配置片段,正好等同于下面的配置片段:

<bean id="theTargetBean" class="..." />

<bean id="client" class="...">
    <property name="targetName" value="theTargetBean"/>
</bean>

第一種形式要比第二種好,因為使用idref標簽可以讓容器在部署時檢驗其引用的,以此命名的bean是否存在。第二個寫法中,沒有對傳遞給bean的targetName屬性的值執行驗證。只有在bean被實例化的時候才會發現錯誤。如果這個bean是一個<font style=text-decoration:underline color=#548e2e>prototype bean</font>,這個錯誤可能會在容器部署成功后很長時間內才被發現。

標簽idref上的local屬性,在4.0版本中的xsd就已經不在支持了,因為它不能再為bean提供一個引用值了。當升級至4.0版本時,只要將你項目中的idref local替換為idref bean即可。

內部bean

<bean/>定義在<property/><constructor-arg/>內,這樣定義的bean稱之為內部bean(inner bean)。

<bean id="outer" class="...">
    <!-- 不再引用一個bean,直接在這里定義一個 -->
    <property name="target">
        <bean class="com.example.Person"> <!-- 這就是內部bean -->
            <property name="name" value="Fiona Apple"/>
            <property name="age" value="25"/>
        </bean>
    </property>
</bean>

內部bean并不需要指定idname屬性。如果指定了,容器并不會識別這兩個標識。容器在創建bean實例的時候也會忽略scope屬性:內部bean應該是匿名的并且總是被伴隨著外部bean來創建的。不可能將內部bean注入到除了它的外部bean以外的任何協作bean中,或者用其他的方式來訪問這個內部bean。

作為一種不常見的情況,有可能從特定的域接受到銷毀的回調函數,例如, request-scoped(請求域)的內部bean包含在單例bean中,創建內部bean實例的過程會綁定到其包含的bean中,但是銷毀的回調允許它參與request-scpoe的生命周期。這不是一個常見的場景。一般內部bean也就是和包含他的bean共享作用域。

Collections

<list/><set/><map/><props/>標簽中,你可以設置屬性和參數,分別是Java集合類型的ListSetMapProperties

<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- 實際調用setAdminEmails(java.util.Properties)方法 -->
    <property name="adminEmails">
        <props>
            <prop key="administrator">administrator@example.org</prop>
            <prop key="support">support@example.org</prop>
            <prop key="development">development@example.org</prop>
        </props>
    </property>
    <!-- 實際調用setSomeList(java.util.List)方法 -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- 實際調用setSomeMap(java.util.Map)方法 -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key ="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- 實際調用setSomeSet(java.util.Set)方法 -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>

map的key/value的值,或者set值,也可以是以下的這些元素:

bean | ref | idref | list | set | map | props | value | null

合并Collection
Spring容器也支持合并Collection。開發者可以在父bean中定義元素<list/><map/><set/><props/>,然后有子中也可以定義<list/><map/><set/><props/>,子類型的bean可以覆蓋和基礎父bean定義的集合元素。其實就是,子的值是合并了父子bean的元素的結果。子中的一個值,父中也有此值,那么會覆蓋父中的值。

本節只討論父子bean的機制。對于不熟悉父子bean的讀者,可以先去讀bean的繼承章節。

下面的例子師范了集合的合并(merge):

<beans>
    <bean id="parent" abstract="true" class="example.ComplexObject">
        <property name="adminEmails">
            <props>
                <prop key="administrator">administrator@example.com</prop>
                <prop key="support">support@example.com</prop>
            </props>
        </property>
    </bean>
    <bean id="child" parent="parent">
        <property name="adminEmails">
            <!-- 注意,merge是在子的集合元素上來指定的 -->
            <props merge="true">
                <prop key="sales">sales@example.com</prop>
                <prop key="support">support@example.co.uk</prop>
            </props>
        </property>
    </bean>
<beans>

注意,子bean的定義中,<props/>標簽上設置了merge="true"。當子bean被容器解析,實例化時,得到的實例有一個adminEmails屬性,這個屬性的值就是子bean屬性adminEmails和父bean屬性````adminEmails```合并后的結果。

administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk

這種合并同樣適用于<list/><map/><set/>等集合類型。使用<list/>場景下,有關List類型的語意,就是仍然保留了有序集合的概念。父中的值優先于子中的值。使用MapSetProperties時并沒有順序的概念。因此,無序語義實際指的就是容器內部使用的集合類型Map,Set和Properties等。

Collection merge的局限性
你不能合并不同的集合類型(例如MapList),如果你這么做就會拋出異常。merge屬性必須用在低級別,繼承的,子的bean定義中;在父級別中設置````merge```是無效的,并不能得到你想要的結果。

強類型的Collection
Java 5中引入泛型之后,您可以使用強類型集合。就是,你可以聲明一個只允許包含String(只是舉一個例子)類型元素的集合。如果你使用Spring去向bean中依賴注入一個強類型集合,那么您可以利用Spring的類型轉換支持,這樣,強類型集合實例的元素就會在添加到集合之前轉換為適當的類型。

public class Foo {

    private Map<String, Float> accounts;

    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
}

相關XML配置:

<beans>
    <bean id="foo" class="x.y.Foo">
        <property name="accounts">
            <map>
                <entry key="one" value="9.99"/>
                <entry key="two" value="2.75"/>
                <entry key="six" value="3.99"/>
            </map>
        </property>
    </bean>
</beans>

當foo的accounts屬性準備好被注入之后,元素的類型即強類型的Map<String, Float>就已經能通過反射得到了。因此,Spring的類型轉換模塊,可以將各種值轉換為Float類型,即上例子中的字符串類型的9.992.753.99被轉換為Float類型的值。

Null和空字符值

Spring認為屬性的的空參數為空字符串。下面的XML配置中,設置email屬性值為空字符串。

<bean class="ExampleBean">
    <property name="email" value=""/>
</bean>

上面的配置等價于下面的Java代碼:

exampleBean.setEmail("");

標簽<null/>可以處理null值,如下:

<bean class="ExampleBean">
    <property name="email">
        <null/>
    </property>
</bean>

上面的配置等同于下面的Java代碼:

exampleBean.setEmail(null);
使用p-namespace來簡寫xml配置

當你定義你的屬性或其他協作bean時候,使用p-namespace(xml命名空間)可以讓你用<bean/>標簽的屬性來替代<property/>標簽。

在XML配置時,Spring支持利用名稱空間來擴展配置。這節討論的bean配置格式僅僅是基于XML配置的。但是p名稱空間并不是在xsd中規定的,它值存在于Spring核心部分中。

下面的例子展示了兩個XML配置片段,他們是解決的是同一問題:第一個是使用標準XML配置格式,第二個使用的是p-namespace。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="classic" class="com.example.ExampleBean">
        <property name="email" value="foo@bar.com"/>
    </bean>

    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="foo@bar.com"/>
</beans>

這個例子展示了在bean定義中,p-namespace下有一個email屬性。這告訴了Spring這是一個聲明屬性的行為。像之前提到的,p-namespace并沒有在xsd中定義,所以你可以將其設置為屬性的名字。

下面的例子仍然包括兩個bean的定義,兩個都引用了另一個bean:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="john-classic" class="com.example.Person">
        <property name="name" value="John Doe"/>
        <property name="spouse" ref="jane"/>
    </bean>

    <bean name="john-modern"
        class="com.example.Person"
        p:name="John Doe"
        p:spouse-ref="jane"/>

    <bean name="jane" class="com.example.Person">
        <property name="name" value="Jane Doe"/>
    </bean>
</beans>

如你所見,這里例子不光使用了p-namespace,還使用一個特殊格式來聲明一個屬性。第一個bean定義使用了<property name="spouse" ref="jane"/>來引用名稱叫jane的bean,第二個bean定義利用p:spouse-ref="jane"作為一個屬性,也引用了bean jane,在這里,spouse是屬性名稱,后半部分的-ref表示這里不是直接值,而是對另一個bean的引用。

p-namespace并不如標準XML定義靈活。例如聲明引用屬性時與以Ref結尾的屬性沖突。我們建議你慎重選擇你的方法,并與團隊成員做好溝通,避免同時使用全部三種方式。

使用c-namespace來簡寫xml配置

和上節的p-namespace類似,c-namespace是在Spring 3.1中引進的,可以用它在行內配置構造方法的參數,可以替代constructor-arg標簽。

讓我們來回顧在章節 基于構造方法依賴注入 中的例子,并用c-namespace來重寫一下:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:c="http://www.springframework.org/schema/c"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="bar" class="x.y.Bar"/>
    <bean id="baz" class="x.y.Baz"/>

    <!-- 之前的方法 -->
    <bean id="foo" class="x.y.Foo">
        <constructor-arg ref="bar"/>
        <constructor-arg ref="baz"/>
        <constructor-arg value="foo@bar.com"/>
    </bean>

    <!-- 利用c-namespace 后 -->
    <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/>

</beans>

使用c-namespacep-namespace的用法類似,同樣的,它也需要被聲明,雖然并沒有在XSD中被規定(但是它在Spring core中可以被識別)。

在一些少見的情景下,例如你無法獲取到構造方法參數名(如果字節碼文件是在沒有調試信息的情況下編譯的),這時可以使用參數位置下標:

<!-- c-namespace 下標聲明 -->
<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>

由于XML的語法,下標的符號需要引入"_"作為屬性的開頭,不能直接以數字開頭!(雖然一些IDE是允許的)

在實踐中,構造函數解析機制在匹配參數方面是非常有效的,所以除非逼不得已才像上面這么做,我們建議在配置中使用名稱表示法來貫穿整個配置。

復合屬性名

當設置bean的屬性時,你可以使用復合的,嵌套的屬性名稱,只要每一級的名稱都不為null。考慮下面的配置:

<bean id="foo" class="foo.Bar">
    <property name="fred.bob.sammy" value="123" />
</bean>

beanfoo有一個屬性叫fredfred有一個叫bob的屬性,bob有一個sammy的屬性,然后最終的屬性sammy的值被設置為123。為了讓它起作用,bean的被構建后,foo的屬性fredfred的屬性bob不能為null,否則,就會拋出NullPointerException(空指針異常)

使用 depends-on

如果一個bean是另一個bean的依賴,通常意味著一個bean被設置為另一個bean的屬性,你通常利用<ref/>屬性就可以搞定。然而,兩個bean之間的關系并不那么直接;例如,Java類中一個靜態的初始化方法需要被觸發。depends-on屬性可以明確的強制一個或多個bean在其屬性值所指定的bean初始化后再進行初始化。可以參考下面的例子,depends-on屬性來指定對一個bean有依賴:

<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />

如果要依賴多個bean,只需要提供給depends-on屬性多個值即可,利用逗號,空格,分號來分割。

<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
    <property name="manager" ref="manager" />
</bean>

<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />

bean定義中的depends-on屬性,可以指定初始化時候的依賴,在單例bean中也可以指定銷毀時間的依賴。在此bean本身被銷毀之前,被指定的依賴bean首先被銷毀,因此,depends-on也可以控制關閉的順序。

懶加載bean

默認的ApplicationContext初始化實現是馬上加載所有的bean。通常這么做是可取的,因為配置錯誤,環境錯誤會立即被發現,而不是過了數小時或數天之后被發現。當不需要這么做時,你可以通過配置bean定義為lazy-init(懶加載)來阻止bean的預實例化。配置好懶加載的bean,會告訴IoC容器,在需要使用這個bean實例的時候再加載這個bean,而不是容器初始化時立即加載這個bean。

在XML配置中,通過配置<bean/>標簽中lazy-init屬性來實現懶加載;下面是例子:

<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>

容器ApplicationContext在使用上面配置時,ApplicationContext不會再啟動時馬上加載叫lazy的bean,叫not.lazy的bean會被馬上加載。

然而,當一個懶加載的bean是一個非懶加載的單例bean的依賴時,ApplicationContext會在啟動時立即實例化這個懶加載bean,這是因為容器必須要提供給這個單例bean的依賴。將懶加載的bean注入到其他非懶加載單例bean中。

你也可以利用<beans/>標簽的default-lazy-init屬性,在容器級別就控制好懶加載:

<beans default-lazy-init="true">
    <!-- 沒有bean會被提前實例化... -->
</beans>
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容