Spring基礎(一)

本來是準備看一看Spring源碼的。然后在知乎上看到來一個帖子,說有一群**自己連Spring官方文檔都沒有完全讀過就嚷嚷著怎么學習Spring源碼,這句話戳中了我的心吶,這不就是說我的嘛。然后自己自己想了一下,之前確實了為了源碼而去讀源碼,所以這次就打算仔細讀一個Spring的官方文檔先。

第二章 Spring框架概述

Spring框架的各個特性被組織成20個模塊。這些模塊被分組成Core Container(核心容器), Data Access/Integration(數據訪問/集成), Web(網絡端), AOP (Aspect Oriented Programming,切面編程), Instrumentation, Messaging(消息),和Test(測試), 以下圖片顯示的就是Spring的各個模塊:


image

2.2.1 核心容器

核心容器 包含了 spring-core, spring-beans, spring-context, and spring-expression (Spring表達式語言) 四個模塊。

spring-core和spring-beans模塊提供了整個框架最基礎的部分, 包括了IoC(控制反轉)和Dependency Injection(依賴注入)特性。 BeanFactory實現了工廠模式。
Context (spring-context)模塊建立在Core and Beans模塊提供的基礎之上: 它提供了框架式訪問對象的方式,類似于JNDI注冊。 Context模塊從Beans模塊中繼承了它的特性并且為國際化(例如使用資源包), 事件傳播, 資源加載和創建上下文,例如Servlet容器。 Context模塊也支持Java EE特性,例如EJB, JMX,和基礎遠程. ApplicationContext接口是Context模塊的焦點所在。
spring-expression模塊提供了一種強大的用于在運行時查詢操作對象的表達式語言。他是對于在JSP2.1規范中所聲明的unified expression語言(統一表達式語言)的擴展。 這種語言支持對屬性值, 屬性參數, 方法調用, 獲得數組內容, 收集器和索引, 算術和邏輯運算, 變量命名和從Spring IoC容器中根據名稱獲得對象。它也為列表映射和選擇提供了支持,就像常見的列表操作一樣。

分離的spring-aspects模塊集成了AspectJ。

2.2.2 AOP

spring-aop模塊提供了AOP面向切面的編程實現,允許你定義,例如, 將攔截器方法和切入點的代碼完全分離開來。 利用源碼中的元數據, 你可以將行為信息加入到你的代碼中, 一定程度上類似于.NET屬性。

2.2.3 Messaging

Spring 4框架包含了spring-messaging模塊,包含了 Spring Integration項目的高度抽象,我個人理解的messaging就是Spring實現了自己的消息隊列。

2.2.4 Data Access/Integration

Spring的集成層面主要包括對于JDBC(spring-jdbc),ORM(spring-orm)和事務(spring-tx)的集成.

2.2.5 Web

Spring的Web模塊主要包括了spring-web, spring-webmvc和spring-websocket。
spring-web模塊主要是包括了例如文件上傳,通過Servlet Listener初始化IOC容器等等web的基礎功能。
spring-webmvc提供了Spring自己的MVC實現,Spring的MVN模塊在Model 層和Web的表單層面做了一個很明確的分割。

2.2.6 Test

spring-test模塊支持JUint和TestNG等集成測試環境。支持統一加載ApplicationContext和緩存這些對戲那個。而且spring-test還提供了mock功能可以幫助你在隔離的環境中測試你的代碼~

2.3.1 Spring依賴管理

我們在項目中經常遇到Spring多版本的情況下,如果我們依賴的其他工程中使用了Spring的其他版本,那么在我們的依賴中就會出現多個版本,在spring 版本比較多的情況下就有能出現一些因為版本不兼容的異常,為了解決這些問題,Spring推出了spring-framework-bom,我們可以這樣子配置bom文件在我們的pom文件中:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-framework-bom</artifactId>
            <version>4.1.3.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

這樣配置之后我們只需要在后面的依賴中添加spring的artifactId和groupId就可以,版本會默認使用上面指定的版本。

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
    </dependency>
<dependencies>

2.3.2 Logging

日志記錄非常重要在,主要有一下幾個原因:

  • 它是唯一一個強制的依賴
  • 用戶都希望能在他們使用的框架中看到一些可用的輸出信息
  • spring依賴的很多集成框架都有自己的日志依賴。

spring默認依賴的日志框架是commons-logging,但是在大多數的使用場景中我們都會提供自己的logging實現,如果要替換掉commons-logging的話,一般需要這兩個步驟:

  • 在spring-core中排除對于commons-logging的依賴
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>4.1.3.RELEASE</version>
        <exclusions>
            <exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>
  • 提供其他的logging框架替換commons-logging
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>4.1.3.RELEASE</version>
        <exclusions>
            <exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!--將spring 對于jcl規則的實現導向slf4j-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>1.5.8</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.5.8</version>
    </dependency>
    <!--使用log4j來作為slf4j的具體實現-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.5.8</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.14</version>
    </dependency>
</dependencies>

如果使用lokback的話就不需要這么復雜,因為logback直接實現了log4j,所以只需要添加jcl-over-slf4j and logback包就可以了。

5. IoC 容器

5.1 Spring IoC容器和Bean概述

org.springframework.beans和org.springframework.context包是Spring框架IoC容器的基礎。 BeanFactory接口提供了一個先進的配置機制能夠管理任何類型的對象。 ApplicationContext(應用上下文) 是BeanFactory的一個子接口。它增加了更方便的集成Spring的AOP功能、消息資源處理(使用國際化)、事件發布和特定的應用層,如在web應用層中使用的WebApplicationContext。在Spring中,被Spring IoC 容器 管理的這些來自于應用主干的這些對象稱作 beans 。bean是一個由Spring IoC容器進行實例化、裝配和管理的對象。此外,bean只是你應用中許多對象中的一個。Beans以及他們之間的 依賴關系 是通過容器使用 配置元數據 反應出來。

5.2 容器概述

org.springframework.context.ApplicationContext接口代表了Spring IoC容器,并且負責上面提到的Beans的實例化、配置和裝配。容器通過讀取配置元數據獲取對象如何實例化、配置和裝配的指示。配置元數據可以用XML、Java注解或Java代碼來描述。它允許你表示組成你應用的對象,以及對象間豐富的依賴關系。

Spring提供了幾個開箱即用的ApplicationContext接口的實現。在獨立的應用程序中,通常創建 ClassPathXmlApplicationContext 或 FileSystemXmlApplicationContext的實例。 雖然XML是定義配置元數據的傳統格式,但是你可以指示容器使用Java注解或者代碼作為元數據格式,你需要通過提供少量XML配置聲明支持這些額外的元數據格式。

5.2.1 配置元數據

Spring IoC容器使用了一種 配置元數據 的形式,這些配置元數據代表了你作為一個應用開發者告訴Spring容器如何去實例化、配置和裝備你應用中的對象。基于XML配置的元數據 不是 唯一允許用來配置元數據的一種形式。Spring IoC容器本身是 完全 和元數據配置書寫的形式解耦的。
Spring配置包括至少一個且通常多個由容器管理的bean定義。在基于XML配置的元數據中,這些beans配置成一個<bean/>元素,這些<bean/>元素定義在頂級元素<beans/>的里面。在Java配置中通常在一個@Configuration注解的類中,在方法上使用@Bean注解。
下面的例子演示了基于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>
5.2.2 實例化容器

實例化Spring IoC容器很容易。將一個或多個位置路徑提供給ApplicationContext的構造方法就可以讓容器加載配制元數據,可以從多種外部資源進行獲取,例如文件系統、Java的CLASSPATH等等。

ApplicationContext context =
    new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});

bean定義可以跨越多個XML文件是非常有用的。通常每個獨立的XML配置文件表示一個邏輯層或者是你架構中的一個模塊。

你可以使用應用上下文的構造方法從多個XML片段中加載bean的定義。像上面例子中出現過的一樣,構造方法可以接收多個Resource位置。或者可以在bean定義中使用一個或多個<import/>從其他的配置文件引入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定義從services.xml、messageSource.xml和themeSource.xml這三個文件中加載。所有的位置路徑都是相對于定義執行導入的文件,所以 services.xml必須和當前定義導入的文件在相同的路徑下。而messageSource.xml和themeSource.xml必須在當前定義導入的文件路徑下的resources路徑下。你可以看到,這里忽略了反斜杠,由于這里的路徑是相對的,因此建議 不使用反斜杠。這些被引入文件的內容會被導入進來,包含頂層的<beans/>元素,它必須是一個符合Spring架構的有效的XML bean定義。

5.2.3 使用容器

ApplicationContext是智能的工廠接口,它能夠維護注冊不同beans和它們的依賴。通過使用 T getBean(String name, Class<T> requiredType) 方法,你可以取得這些beans的實例。

// 創建并配置beans
ApplicationContext context =
    new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});

// 取得配置的實例
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// 使用實例
List<String> userList = service.getUsernameList();

使用getBean()來獲取您beans的實例,ApplicationContext接口還有幾個其他的可以獲取beans的方法,但是理想情況下,你最好不要使用這些方法。 事實上,您的應用程序代碼不應調用getBean()所有方法,因而不會和Spring的接口產生依賴。

5.3 Bean概述

一個Spring IoC容器管理了一個或者多個 beans。這些beans通過你提供給容器的配置元數據進行創建,例如通過XML形式的<bean/>定義。
在容器內本身,這些bean定義表示為BeanDefinition對象,它包含了如下的元數據:

  • 包限定的類名: 通常是bean定義的實現類。
  • Bean行為配置元素,這些狀態指示bean在容器中的行為(范圍,生命周期回調函數,等等)。
  • bean工作需要引用的其他beans;這些引用也稱為 協作者 或 依賴者。
  • 其他配置設置應用于新創建的對象中設置,例如連接池中的連接數或者連接池的大小限制。

ApplicationContext實現還允許由用戶在容器外創建注冊現有的對象。 這是通過訪問ApplicationContext的工廠方法,通過getBeanFactory()返回DefaultListableBeanFactory工廠方法的實現。 DefaultListableBeanFactory支持通過registerSingleton(..)方法和registerBeanDefinition(..)方法進行注冊。 然而,典型的應用程序的工作僅僅通過元數據定義的bean定義beans。

5.3.1 bean的命名

每個bean都有一個或多個標識符,這些bean的標識符在它所在的容器中必須唯一。 一個bean通常只有一個標識符,但如果它有一個以上的id標識符,多余的標識符將被認為是別名。

  • 通過構造函數實例化 當你使用構造方法來創建bean的時候,Spring對class沒有特殊的要求。也就是說,正在開發的類不需要實現任何特定的接口或者以特定的方式進行編碼。但是,根據你使用那種類型的IoC來指定bean,你可能需要一個默認(無參)的構造方法。
  • 使用靜態工廠方法實例化 下面的bean定義展示了如何通過工廠方法來創建bean實例。注意,此定義并未指定返回對象的類型,僅指定該類包含的工廠方法。在此例中,createInstance()必須是一個static方法。
<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屬性必須為空,而factory-bean屬性必須指定為當前(或其祖先)容器中包含工廠方法的bean的名稱,而該工廠bean的工廠方法本身必須通過factory-method屬性來設定。
<!-- 工廠bean,包含createInstance()方法 -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- 其他需要注入的依賴項 -->
</bean>

<!-- 通過工廠bean創建的ben -->
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();
    private DefaultServiceLocator() {}

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}

5.4 依賴

5.4.1 依賴注入

構造器參數解析 構造器參數通過參數類型進行匹配。如果構造器參數的類型定義沒有潛在的歧義,那么bean被實例化的時候,bean定義中構造器參數的定義順序就是這些參數的順序并依次進行匹配。看下面的代碼:

package x.y;

public class Foo {

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

<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被引用,它的類型是已知的,并且匹配也沒問題(跟前面的例子一樣)。當我們使用簡單類型,比如<value>true</value>。Spring并不能知道該值的類型,不借助其他幫助Spring將不能通過類型進行匹配。


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>

你也可以使用構造器參數命名來指定值的類型:
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>
請記住為了使這個起作用,你的代碼編譯時要打開編譯模式,這樣Spring可以檢查構造方法的參數。如果你不打開調試模式(或者不想打開),也可以使用 @ConstructorProperties JDK注解明確指出構造函數的參數。下面是簡單的例子:

public class ExampleBean {
    // Fields omitted
    @ConstructorProperties({"years", "ultimateAnswer"})
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }

}

ApplicationContext所管理的beans支持構造函數注入和setter注入,在一些依賴已經使用構造器注入之后它還支持setter注入。你可以以BeanDefinition的形式配置依賴,它能根據指定的PropertyEditor實現將屬性從一種格式轉化為另外一種格式。但是,大多數Spring的使用者不會直接使用這些類(也就是通過編程的形式),而是采用XML配置這些bean,注解的組件(即用@Component,@Controller等注解類),或者基于@Configuration類的@Bean方法。本質上這些資源會轉換成BeanDefinition的實例并且用于加載整個Spring IoC容器實例。

因為你可以混合使用構造器注入和setter注入, 強制性依賴關系 時使用構造器注入, 可選的依賴關系 時使用setter方法或者配置方法是比較好的經驗法則。
通常你可以信賴Spring。在容器加載時Spring會檢查配置,比如不存在的bean和循環依賴。當bean創建時,Spring盡可能遲得設置屬性和依賴關系。這意味著即使Spring正常加載,在你需要一個存在問題或者它的依賴存在問題的對象時,Spring會報出異常。舉個例子,bean因設置缺少或者無效的屬性會拋出一個異常。因為一些配置問題存在將會導致潛在的可見性被延遲,所以默認ApplicationContext的實現bean采用提前實例化的單例模式。在實際需要之前創建這些bean會帶來時間和內存的開銷,當ApplicationContext創建完成時你會發現配置問題,而不是之后。你也可以重寫默認的行為使得單例bean延遲實例化而不是提前實例化。

5.4.2 依賴配置詳解

  • 直接變量 (基本類型, String類型等) <property/>元素的value值通過可讀的字符串形式來指定屬性和構造器參數。Spring的conversion service 把String轉換成屬性或者構造器實際需要的類型。
<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>

接下來的例子使用p 命名空間簡化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>

你也可以配置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>
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是否存在。 在第二種形式中,傳給 client bean中屬性targetName的值并沒有被驗證。 只有當 client bean完全實例化的時候錯誤才會被發現(可能伴隨著致命的結果)。
idref和ref的區別就是idref只能夠引用其他bean的id,而ref可以引用if或者name

  • 內部bean 所謂的內部bean就是指在 <property/> 或者 <constructor-arg/> 元素內部使用<bean/>定義bean。
  • 集合 在<list/>, <set/>, <map/>, 和<props/>元素中,你可以設置值和參數分別對應Java的集合類型List, Set, Map, 和 Properties
  • 集合合并 Spring容器也支持集合的 合并。開發者可以定義parent-style<list/>,<map/>, <set/> 或者<props/> 元素并, child-style 的<list/>, <map/>, <set/> 或者 <props/>元素繼承和覆蓋自父集合。也就是說。父集合元素合并后的值就是子集合的最終結果,而且子集中的元素值將覆蓋父集中對應的值。
<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">
            <!-- the merge is specified on the child collection definition -->
            <props merge="true">
                <prop key="sales">sales@example.com</prop>
                <prop key="support">support@example.co.uk</prop>
            </props>
        </property>
    </bean>
<beans>
  • 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)
5.4.3 使用 depends-on

如果一個bean是另外一個bean的依賴,這通常意味著這個bean可以設置成為另外一個bean的屬性。當前bean初始化之前顯式地強制一個或多個bean被初始化。下面的例子中使用了depends-on屬性來指定一個bean的依賴。
為了實現多個bean的依賴,你可以在depends-on中將指定的多個bean名字用分隔符進行分隔,分隔符可以是逗號,空格以及分號等。

<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" />
5.4.4 延遲初始化bean

ApplicationContext實現的默認行為就是再啟動時將所有singleton bean提前進行實例化。 通常這樣的提前實例化方式是好事,因為配置中或者運行環境的錯誤就會被立刻發現,否則可能要花幾個小時甚至幾天。如果你不想 這樣,你可以將單例bean定義為延遲加載防止它提前實例化。

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

但是當一個延遲加載的bean是單例bean的依賴,但這個單例bean又不是 延遲加載,ApplicationContext在啟動時創建了延遲加載 的bean,因為它必須滿足單例bean的依賴。因此延遲加載的bean會被注入單例bean,然而在其他地方它不會延遲加載。

5.4.6 方法注入

在大部分的應用場景中,容器中的大部分bean是singletons類型的。當一個單例bean需要和另外一個單例bean, 協作時,或者一個費單例bean要引用另外一個非單例bean時,通常情況下將一個bean定義為另外一個bean的屬性值就行了。不過對于具有不同生命周期的bean 來說這樣做就會有問題了,比如在調用一個單例類型bean A的某個方法,需要引用另一個非單例(prototype)類型bean B,對于bean A來說,容器只會創建一次,這樣就沒法 在需要的時候每次讓容器為bean A提供一個新的bean B實例。
上面問題的一個解決方法是放棄控制反轉,你可以實現ApplicationContextAware接口來讓bean A感知到容器, 并且在需要的時候通過使用使用getBean("B")向容器請求一個(新的)bean B實例。下面的例子使用了這個方法:

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        // grab a new instance of the appropriate Command
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

Lookup 方法注入 Lookup方法具有使容器覆蓋受容器管理的bean方法的能力,從而返回指定名字的bean實例。在上述場景中,Lookup方法注入適用于原型bean。 Lookup方法注入的內部機制是Spring利用了CGLIB庫在運行時生成二進制代碼的功能,通過動態創建Lookup方法bean的子類從而達到復寫Lookup方法的目的.

public abstract class CommandManager {

    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="command"/>
</bean>
標識為commandManager的bean在需要一個新的command bean實例時會調用createCommand()方法。你必須將`command`bean部署為 原型(prototype)類型,如果這是實際需要的話。如果部署為singleton。那么每次將返回相同的 `command`bean。

5.5 Bean作用域

當你創建一個 bean 的定義,實際上是創建了一個產生真實實例的配方(recipe)。bean 定義是一個配方(recipe)這種概念是很重要的,它的意思是指,和class一樣,你可以從一個配方(recipe)創建多個對象實例。

5.5.1 單例作用域

僅管理一個單例 bean 的共享實例,并且所有通過 id 或者 ids 獲得 bean 定義的請求,都會從 Spring 容器中得到同一個特定的 bean 實例。

5.5.2 原型作用域

bean使用原型作用域而不是單例作用域的話,會在每次請求該bean,也就是bean被注入至另一個bean、 或通過調用Spring容器的 getBean() 方法時,創建一個新的bean實例 。 通常,對于有狀態的bean使用原型作用域,無狀態的bean則使用單例作用域。
與其他作用域不同的是,Spring容器不會管理原型域bean的完整生命周期:Spring容器會初始化、 配置,亦或者組裝原型域的bean對象,然后交給客戶端,之后就再也不會管這個bean對象了。 因此,對于bean的生命周期方法來說,盡管所有作用域的 初始化方法 都會被調用, 但是原型域bean的 銷毀方法 不會 被Spring容器調用。客戶端代碼要自己負責銷毀原型域bean 以及和bean相關的資源(特別是開銷大的資源)。如果想讓Spring負責這些事(銷毀bean、釋放資源), 就得自定義bean的后處理器 bean post-processor ,它會持用原型域bean的引用。

5.5.3 依賴原型bean的單例bean

如果你的單例bean依賴了原型bean,謹記這些依賴(的原型bean) 只在初始化時解析 。 因此,假如你將原型bean依賴注入至單例bean,在注入時會初始化一個新的原型bean實例, 這個被注入的原型bean實例是一個獨立的實例。

5.5.4 請求作用域、會話作用域和全局會話作用域

僅當 你使用web相關的Spring ApplicationContext(例如 XmlWebApplicationContext)時, 請求 request 、會話 session 和全局會話 global session 作用域才會起作用。如果你在普通的Spring IoC 容器(例如 ClassPathXmlApplicationContext)中使用這幾個作用域,會拋出異常 IllegalStateException ,告知你這是一個未知的bean作用域

5.6 定制bean特性

5.6.1 生命周期回調

Spring提供了幾個標志接口(marker interface),這些接口用來改變容器中bean的行為;它們包括InitializingBean和DisposableBean。 實現這兩個接口的bean在初始化和析構時容器會調用前者的afterPropertiesSet()方法,以及后者的destroy()方法。
在現代的Spring應用中,The JSR-250 @PostConstruct and @PreDestroy 接口一般認為是接收生命周期回調的最佳做法。 使用這些注解意味著bean沒有耦合到Spring具體的接口。
Spring在內部使用 BeanPostProcessor 實現來處理它能找到的任何回調接口并調用相應的方法。如果你需要自定義特性或者生命周期行為,你可以實現自己的 BeanPostProcessor 。

初始化回調函數

實現 org.springframework.beans.factory.InitializingBean 接口,允許容器在設置好bean的所有必要屬性后,執行初始化事宜。通常,要避免使用 InitializingBean 接口并且不鼓勵使用該接口,因為這樣會將代碼和Spring耦合起來。 使用@PostConstruct注解或者指定一個POJO的初始化方法。 在XML配置元數據的情況下,使用 init-method 屬性去指定方法名,并且該方法無參數簽名。

析構回調函數

實現 org.springframework.beans.factory.DisposableBean 接口,允許一個bean當容器需要其銷毀時獲得一次回調。建議不使用 DisposableBean 回調接口,因為會與Spring耦合。使用@PreDestroy 注解或者指定一個普通的方法,但能由bean定義支持。基于XML配置的元數據,使用 <bean/> 的 destroy-method 屬性。

組合生命周期機制

截至 Spring 2.5,有三種選擇控制bean生命周期行為:InitializingBean 和 DisposableBean 回調接口;自定義init() 和 destroy() 方法; @PostConstruct and @PreDestroy。如果bean存在多種的生命周期機制配置并且每種機制都配置為不同的方法名, 那所有配置的方法將會按照上面的順利執行。順序依次是:注解->afterPropertiesSet()->init()方法。

5.6.2 ApplicationContextAware and BeanNameAware

當 ApplicationContext 創建一個實現 org.springframework.context.ApplicationContextAware 接口的對象的實例, 該實例提供一個參考,ApplicationContext。bean可以通過編程方式操縱 ApplicationContext 來創建,通過 ApplicationContext 接口,或者通過向這個接口的一個已知的子類的引用, 如 ConfigurableApplicationContext ,公開附加功能。截止Spring 2.5,自動裝配是獲取 ApplicationContext 引用傳統的 constructor 和 byType自動模式(在 Section 5.4.5, “自動裝配協作者”中描述)可以分別為 ApplicationContext 類型的構造函數參數或setter方法參數提供依賴。比較方便的做法直接通過@Autowired將屬性注入進來。,但是基于xml的配置也是完全可以的~
當 ApplicationContext 創建一個實現 org.springframework.beans.factory.BeanNameAware 接口的類,該類為 定義在其相關對象定義中的名稱提供一個索引。實現方式和上面的ApplicationContextAware一致~

5.6.3 其他 Aware 接口

除了上述的 ApplicationContextAware 和 BeanNameAware ,Spring提供一系列的 Aware 接口,允許bean表示他們需要一定基礎設施依賴的容器。 最重要的 Aware 接口概括如下,作為一般規則,這個名稱是依賴類型的一個很好的指示:
再次說明,這些接口的使用將您的代碼耦合到Spring API,并且不遵循反轉控制方式。

  • ApplicationContextAware 聲明ApplicationContext
  • ApplicationEventPublisherAware 封閉的事件發布者 ApplicationContext
  • BeanClassLoaderAware 用于裝載bean class 的裝載器.
  • BeanFactoryAware 聲明 BeanFactory
  • BeanNameAware 聲明bean的名稱
  • ServletConfigAware 目前 ServletConfig 容器運行. 僅在一個web-aware Spring中有效 ApplicationContext
  • ServletContextAware 目前 ServletContext 容器運行.

5.7 Bean定義的繼承

在一個bean定義中,可以包含配置信息,包括構造器參數,屬性值,以及容器的特定信息,比如初始化方法, 靜態工廠方法名等等。子bean定義,從它的父bean定義繼承配置數據;子bean定義可以覆蓋一些值, 或者根據需要添加一些其他值。使用父子bean定義,可以避免很多配置填寫。事實上,這是一種模板設計模式。

<bean id="inheritedTestBean" abstract="true"
        class="org.springframework.beans.TestBean">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithDifferentClass"
        class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBean" init-method="initialize">
    <property name="name" value="override"/>
    <!--age屬性的值1,將會從父類繼承-->
</bean>

下面的例子,通過使用abstract屬性,明確地標明這個父類bean定義是抽象的。如果,父類bean定義 沒有明確地指出所屬的類,那么標記父bean定義為為abstract是必須的,如下:

<bean id="inheritedTestBeanWithoutClass" abstract="true">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBeanWithoutClass" init-method="initialize">
    <property name="name" value="override"/>
    <!--age屬性的值1,將會從父類繼承-->
</bean>

這個父bean不能自主實例化,因為它是不完整的,同時它也明確地被標注為abstract;像這樣, 一個bean定義為abstract 的,它只能作為一個純粹的bean模板,為子bean定義,充當父bean定義。 嘗試獨立地使用這樣一個abstract的父bean,把他作為另一個bean 的引用,或者根據這個父bean的id顯式調用getBean()方法, 將會返回一個錯誤。類似地,容器內部的preInstantiateSingletons() 方法,也忽略定義為抽象的bean定義。

5.8 容器拓展點

5.8.1 使用BeanPostProcessor自定義beans

BeanPostProcessor 定義了回調方法,通過實現這個回調方法,你可以提供你自己的(或者重寫容器默認的) 實例化邏輯,依賴分析邏輯等等。如果你想在Spring容器完成實例化配置,實例化一個bean之后,實現一些自定義邏輯 你可以插入一個或多個 BeanPostProcessor 的實現。

你可以配置多個BeanPostProcessor實例,同時你也能通過設置 order 屬性來控制這些BeanPostProcessors 的執行順序。只有BeanPostProcessor實現了Ordered 接口,你才可以設置 order 屬性。如果,你編寫了自己的BeanPostProcessor 也應當考慮實現 Ordered 接口。欲知詳情,請參考BeanPostProcessor 和 Ordered接口的javadoc。
BeanPostProcessors作用范圍是每一個容器。這僅僅和你正在使用容器有關。如果你在一個容器中定義了一個BeanPostProcessor ,它將 僅僅 后置處理那個容器中的beans。換言之,一個容器中的beans不會被另一個,容器中的BeanPostProcessor處理,即使這兩個容器,具有相同的父類。
一個ApplicationContext,自動地檢測所有定義在配置元文件中,并實現了BeanPostProcessor接口的bean。 該ApplicationContext注冊這些beans作為后置處理器,使他們可以在bean創建完成之后,被調用。 bean后置處理器可以像其他bean一樣部署到容器中。

  • 編程式注冊 BeanPostProcessors 雖然推薦使用ApplicationContext的自動檢測來注冊BeanPostProcessor,但是對于使用了addBeanPostProcessor方法的ConfigurableBeanFactory也可以編程式地注冊他們。 在注冊之前,或者是在繼承層次的上下文之間復制bean后置處理器,需要對邏輯進行條件式的評估時,這是有用的。但是請注意,編程地添加的BeanPostProcessors 不需要考慮Ordered接口 。 也就是注冊的順序決定了執行的順序。也要注意,編程式注冊的BeanPostProcessors,總是預先被處理----早于通過自動檢測方式注冊的,同時忽略 任何明確的排序
  • BeanPostProcessors 和 AOP 自動代理 實現了BeanPostProcessor接口的類是特殊的,會被容器特殊處理。所有BeanPostProcessors和他們直接引用的 beans都會在容器啟動的時候被實例化,作為`ApplicationContext特殊啟動階段的一部分。接著,所有的BeanPostProcessors 以一個有序的方式,進行注冊,并應用于容器中的一切bean。因為AOP自動代理本身被實現為BeanPostProcessor, 這個BeanPostProcessors 和它直接應用的beans都沒有資格進行自動代理,這樣就沒有切面編織到他們里面。
BeanPostProcessor使用的實例
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // simply return the instantiated bean as-is
    public Object postProcessBeforeInitialization(Object bean,
            String beanName) throws BeansException {
        return bean; // we could potentially return any object reference here...
    }

    public Object postProcessAfterInitialization(Object bean,
            String beanName) throws BeansException {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }

}
5.8.2 通過BeanFactoryPostProcessor來處理元數據

BeanFactoryPostProcessor和BeanPostProcessor非常像,但還有一個明顯的區別:BeanFactoryPostProcessor主要處理的是bean的定義元數據,Spring允許BeanFactoryPostProcessor讀取bean定義的元數據并在bean的實例化之前改變其中的某些元素。它的作用域和BeanPostProcessor保持一致,一個容器中的BeanFactoryPostProcessor只會在當前元素中生效,我們可以通過實現Orderd接口來決定BeanFactoryPostProcessor的生效順序。Spring在內部已經生成了很多BeanFactoryPostProcessor的實現類,比較常見的就是PropertyPlaceholderConfigurer,用來進行占位符的替換操作。

PropertyPlaceholderConfigurer例子
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations" value="classpath:com/foo/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

通過對于PropertyPlaceholderConfigurer的聲明,我們可以使用jdbc.proerties文件中的屬性替換占位符對象。在Spring2.5之后更是提供了注解形式的寫法

<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>

Spring并不只會讀取priperties文件,還會在沒有讀取到配置信息的時候進一步讀取System配置信息。我們可以通過systemPropertiesMode來控制對于Java System元素的讀取優先級:

  • 0 :從來不會讀取系統配置
  • 1 :如果沒有在配置文件中讀取到數據的時候,嘗試從System配置中讀取。(默認情況)
  • 2 :先讀取System配置,然后在讀取配置文件
PropertyOverrideConfigurer使用樣例

PropertyOverrideConfigurer可以用來設置bean的屬性值,如果配置文件中沒有包含對應bean的屬性值的話,默認的屬性值就會生效。

//db.proerties
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb

foo.fred.bob.sammy=123

<context:property-override location="classpath:db.properties"/>

然后,名為dataSource的bean的driverClassName屬性和url屬性都會被替換為系統中的值,嵌套的屬性值也會被替換掉,例如:foo.fred.bob.sammy

5.9 注解配置

注解和XML那個更屌?一千個人眼中有一千個哈姆雷特,針對這個問題大家的看法也都不一致。但是我常用給的做法是這樣子:一些對于第三方依賴配置我會使用xml配置,例如db層,(kafka)隊列介入等。對于內部使用的Bean,例如Service,Dao,Conmopnet等等都是通過注解來實現的,特別是對于Bean屬性的注入等等都是通過注解的。總結的話使用比例大概在7:3,部分簡單的配置也會直接用@Configuration替換xml配置(偷懶)。但是注解的缺點就是與Spring代碼嚴重耦合,如果注解多的情況下代碼看起來可能會比較亂,維護的地方比較分散,不像xml那樣基本只會分布在resource目錄的。

注解是在xml配置生效前生效的,所以如果注解和xml同時都配置來某個信息,xml的配置會覆蓋注解的。
通常情況下,如果需要使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"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

</beans>

這個配置主要告訴Spring激活AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor, PersistenceAnnotationBeanPostProcessor和RequiredAnnotationBeanPostProcessor.

  • @Required
    @Required注解用在類的set方法上,表明該屬性必須在加載配置的時候被加載進來,如果需要的屬性沒有傳進來的話會拋出異常。

  • @Autowired 這個注解應該是我們用的最多的注解了,@Autowired是根據type進行判斷然后注入的,所以如果有多個接口實現類的話使用@Autowired是有問題的。@Autowired可以使用在屬性上或者set方法上。如果一個接口有多個實現,我們要一次性注入進來的話可以這么做(下面代碼):在被注入的對象是map的情況下,map的key一定是String,key的內容就是具體的beanName,value是具體的實現類的引用。默認情況下@Autowired標注的屬性是必須的,如果沒有合適的提供者的話會報錯,如果不一定需要的話可以通過屬性@Autowired(required=false)來標注。在使用@Autowired的時候你不緊可以注入自己的實現類,還可以注入Spirng 的一些已知類,例如BeanFactory, ApplicationContext, Environment, ResourceLoader, ApplicationEventPublisher或者 MessageSource。

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;
}

public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;
    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }
}
  • @Qualifier 這個注解通常和@Autowired配合使用,如果一個接口有兩個實現的話,單純使用@Autowired就會報錯,因為這個時候如果我們想指定其中一個Bean的話就需要配合@Qualifier,表示需要某種限定符標示的bean。@Qualifier還可以直接用在構造函數的參數或者set方法的參數上,例如(如下代碼)。但是對于@Qualifier有一點值得說明的地方就是它并不標示“唯一”,可以有多個bean配置了同樣的Qualifier,@Qualifier主要是用來區分某一類,而不是某一個。我們可以在Set<MovieCatalog> logs屬性上使用@Qualifier注解標示我們這個set中希望注入的部分bean。
public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(@Qualifier("main")MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...

}
  • @Resource Spring支持 JSR-250提供的 @Resource注解,該注解與@Autowired的主要區別就是@Resource是byName的,@Autowired是byType的。@Resource使用的優先級依次是:先根據name屬性查找對應的bean,如果name沒有指定的話就是用默認的駝峰形式為name來尋找bean,如果依然沒有找到的話則退而根據byType來尋找具體的bean。和@Autowired比較像的是其對于Spring內置的一些Bean尋找,像BeanFactory, ApplicationContext, ResourceLoader, ApplicationEventPublisher, and MessageSource等只需要簡單的配置@Resource就可以了,不需要其他配置。
  • @PostConstruct and @PreDestroy 這兩個注解在Bean的生命周期介紹時候已經有介紹過,分別在bean初始化之后和bean銷毀之前生效。他們可以生效的原因是CommonAnnotationBeanPostProcessor已經在ApplicationContext中注冊過了。
  • @Component @Component是Spring用來聲明組件的統一注解形式,它是一個泛化的聲明。如果可以的話,盡可能的使用更加精確的注解,像@Service,@Controller等等。這些注解往往有更加精確的含義說明。但是要想這些注解生效的話(將Class解析為BeanDefinnition,然后被ApplicationContext使用),你需要在@Configuration注解上配置@ComponentScan,通過basePackages屬性指定掃描的路徑。默認情況下@ComponentScan會掃描@Component, @Repository, @Service, @Controller等注解,如果你不希望掃描這些配置的話,可以自己添加過濾器來掃描指定的類.
@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    ...
}
通過components來定義Bean metadata

Spring還可以通過components或者@Configuration來生成bean definition,你可以在要生成bean的方法上標注@Bean注解。下面是個例子:

@Component
public class FactoryMethodComponent {
    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }
}

這個例子中就通過@Bean來生成一個bean definition。

@Component
public class FactoryMethodComponent {

    private static int i;

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    @Bean
    protected TestBean protectedInstance(
            @Qualifier("public") TestBean spouse,
            @Value("#{privateInstance.age}") String country) {
        TestBean tb = new TestBean("protectedInstance", 1);
        tb.setSpouse(spouse);
        tb.setCountry(country);
        return tb;
    }

    @Bean
    @Scope(BeanDefinition.SCOPE_SINGLETON)
    private TestBean privateInstance() {
        return new TestBean("privateInstance", i++);
    }

    @Bean
    @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
    public TestBean requestScopedInstance() {
        return new TestBean("requestScopedInstance", 3);
    }

}

Spring組件中的@Bean方法與Spring @Configuration類中的對應方法不同。不同之處是,@Component類沒有通過CGLIB增強,以攔截方法和字段的調用。CGLIB代理是在@Configuration類中的@Bean方法中調用方法或字段的方法,它為協作對象創建bean元數據引用;這些方法不是用普通的Java語義調用的。相反,在@Component類中調用@Bean方法中的方法或字段具有標準的Java語義。
當Spring在監測到一個Component的時候,如果注解上沒有定義名稱的話,Spring則會模式給該bean生成一個name,生成的規則還是采用首字母小寫的形式。 如果你想定義自己的默認名字生成規則,可以這個做:
1.實現ScopeMetadataResolver接口
2.配置時候指定自己定義的ScopeMetadataResolver

@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
    ...
}
  • JSR330的標準注解@Inject基本等價與@Autowired,只不過不支持required屬性,@Named基本等價于@Component+@Qualifier,但是因為@Autowired的概念已經比較深入,所以建議還是使用Spring 的注解吧。

5.12 基于Java(Java-based)的容器配置

5.12.1 基本概念: @Bean and @Configuration

Spring新的Java配置支持中核心構件是 @Configuration 注釋類和 @Bean 注釋方法。

@Bean 注釋是用來表示一個方法實例化,配置和初始化一個由Spring IoC容器管理的新的對象。對于那些熟悉Spring的 <beans/> XML 配置, @Bean 注釋和 <bean/> 一樣起著同樣的作用。你可以使用 @Bean 注釋任何Spring @Component 的方法,但是, 最經常使用的是 @Configuration(@Configuration就是一種被@Component標示的注解) 注釋bean。

用 @Configuration 注釋一個類表明它的主要目的是作為bean定義的來源。此外, @Configuration 注釋的類允許inter-bean依賴關系 在相同的類中,通過簡單地調用其他 @Bean 方法被定義。最簡單的 @Configuration 類定義如下:

@Configuration
public class AppConfig {
    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

在使用@Bean注解的時候也可以做到Bean的生命周期管理,如下:

public class Foo {
    public void init() {
        // initialization logic
    }
}

public class Bar {
    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    @Scope("prototype")
    public Foo foo() {
        return new Foo();
    }

    @Bean(name = "testBean", destroyMethod = "cleanup")
    public Bar bar() {
        return new Bar();
    }
    
    //只有在使用@Configuration的時候,內部@Bean才可以使用
    @Bean
    public Demo demo(){
        retrun new Demo(bar());
    }

}
  • @Import注解可以像xml中的import一樣使用,如下:
@Configuration
public class ConfigA {
     @Bean
    public A a() {
        return new A();
    }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }

}

我們在@Configuration生效的時候也可以進行一定的定制,比如在測試環境生效,線上環境不生效這種操作,這時候可以依賴另外一個注解@Conditional,做法就是實現Conditinal,然后在@Conditional的注解上指定其實現類就可以做到有條件的激活。

5.13 Environment抽象

Environment 是集成在容器中的抽象,他包含了兩個兩個方面:profiles 和 properties.

profile是一個命名,是一組邏輯上bean定義的組,只有相應的profile被激活的情況下才會起作用。可以通過XML或者注解將bean分配給一個profile,Environment對象在profile中的角色是判斷哪一個profile應該在當前激活和哪一個profile應該在默認情況下激活。

屬性在幾乎所有應用中都扮演了非常重要的角色,并且可能來源于各種各樣的資源:屬性文件,JVM系統屬性,系統環境變量,JNDI,servlet上下文參數,點對點的屬性對象,映射等等。Environment對象在屬性中的角色是提供一個方便的服務接口來配置屬性資源和解決它們的屬性。

5.13.1 Bean定義配置文件

Beand定義配置文件是核心容器的一種機制,它允許為不同的bean在不同的環境中注冊。 environment這個詞可以意味著不同的事情不同的用戶,而且這個功能可以幫助很多用例,包括:

在工作中使用內存數據源并且在QA和生產環境中通過JDNI查找相同的數據源
只有當部署應用到一個性能測試環境時注冊監視工具
給客戶A注冊定制的bean實現而不需要給客戶B時

@Profile 注解用于當一個或多個配置文件激活的時候,用來指定組件是否有資格注冊。使用上面的例子,我們可以按如下方式重寫dataSource配置:

@Configuration
@Profile("dev")
public class StandaloneDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }
}

如果帶@Configuration的類被標記了@Profile,那么只有當這個配置是激活狀態的時候,這個類中標記@Bean的方法和@Import關聯的類才有效,否則就會被忽略。 如果一個@Component或@Configuration類標記了@Profile({"p1", "p2"}),這樣的類只有當p1和(或)p2激活的時候才有效。如果一個配置使用了!前綴,只有當這個配置不激活的時候才有效。例如@Profile({"p1", "!p2"}),只有當p1激活,p2不激活的時候才有效。

啟用配置文件

現在我們已經更新了我們的配置,我們還需要指示那個配置文件處于激活狀態。如果我們現在啟動我們的示例程序,我們會看到拋出NoSuchBeanDefinitionException異常,因為我們的容器找不到名為dataSource的bean對象。

激活配置文件可以采取多種方式,但是最直接的方式就是以編程的方式使用ApplicationContext API:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("dev");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();

配置文件還可以以聲明的方式通過spring.profiles.active屬性來激活,可以通過系統環境變量,JVM系統屬性。啟動Tomcat的時候只要簡單的加一條啟動參數-Dspring.profiles.active="profile1,profile2"就可以激活目標profile。另外需要注意一點的是,如果一個profile的name配置了“default”的話,是默認激活的項。

5.15.2 Spring標準的和用戶自定義事件

Spring本身也是支持事件監聽機制的,其基本的事件和事件監聽器主要是ApplicationEvent和ApplicationListener接口,如果有ApplicationEvent發布的話,對應的監聽器類就會觸發自己監聽操作,下面介紹一些Spring自己的事件。

  • ContextRefreshedEvent Spring容器的初始化或者refresh都會發布這個事件。
  • ContextStartedEvent Spring容器調用start方法的時候發布此事件
  • ContextStoppedEvent Spring容器調用stop方法的時候發布此事件
  • RequestHandledEvent 這個是一個特殊的web事件,每次有HTTP請求到達的時候都會發布這個事件,但是這個事件只有在請求結束之后才會發布

我們也可以定義自己的事件機制,下面簡單介紹一下:

//定義事件
public class BlackListEvent extends ApplicationEvent {

    private final String address;
    private final String test;

    public BlackListEvent(Object source, String address, String test) {
        super(source);
        this.address = address;
        this.test = test;
    }
}

public class EmailService implements ApplicationEventPublisherAware {

    private List<String> blackList;
    //事件發布者
    private ApplicationEventPublisher publisher;

    public void setBlackList(List<String> blackList) {
        this.blackList = blackList;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void sendEmail(String address, String text) {
        if (blackList.contains(address)) {
            BlackListEvent event = new BlackListEvent(this, address, text);
            publisher.publishEvent(event);
            return;
        }
    }
}

//特殊事件監聽器(通過事件類型區分)
public class BlackListNotifier implements ApplicationListener<BlackListEvent> {

    private String notificationAddress;
    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

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

推薦閱讀更多精彩內容