Spring框架起步,小白都看得懂(官翻版)!

寫在開頭

本篇章介紹Spring框架的完整的所有技術。html

本人偏心Java Configuration(也是Spring實戰推薦的方式),因此一樣配置的狀況下,將略去XML配置不表,請知悉。java

官翻版本:Spring Framework Core 5.2.5.RELEASEmysql

本文會盡可能遵照原文排版,某些我以爲不過重要的,我會只加標題,而不加具體內容,並放上連接,供感興趣的讀者自行閱讀。web

由於考慮到考研,項目開發等,因此文章更新會慢一些,若是你喜歡的話,也能夠私信我提醒更新!算法

須要的基礎知識:spring

Java SE(Version 11.0+)
Maven(Version 3.62+)
JetBrains IntelliJ IDEA(Version 2019.3.4+)
GitHub

出現的專業詞彙講解

配置元數據:配置源或配置文件的意思。sql

依賴:顧名思義,構建某個實例須要的其餘實例,由於依賴於這個實例,因此叫「依賴」。數據庫

Bean:中文翻譯「豆子,黃豆」,就是一個類實例,由框架進行生成和銷燬。apache

DAO: 數據庫訪問類(Data Access Object),就是一類專門用來訪問數據庫的Bean編程

耦合:依賴的意思,A和B耦合性越高,說明它倆互相依賴性越強,軟件設計講究低耦合,是爲了後期維護方便,省得「牽一髮而動全身」。

屬性(Property):屬性,其實就是私有域。

POJO:中文翻譯,簡單Java對象。就是一個類,相似於Bean,做爲數據載體,只有setter和getter方法,有時會覆寫hashCode(), equals(), toString()方法等。

AoolicationContext:應用程序上下文,啥意思呢?都作過閱讀理解吧?有一種題叫「請結合上下文分析XXXX」,沒錯,就是那個上下文,在Spring裏面的意思是,整個程序所處的環境,你能夠在ApplicationContext獲取到Bean,配置文件,系統資源,啓動項信息等。因此「上下文」的意思就是,程序的管理者,它擁有整個程序,能夠滲透到各處進行管理操做。(我當初真的理解了很久!)

回調方法:就是在某個操做完成後,會自動的調用這個方法,在監聽者設計模式中有詳細的說明。

緒論

在Spring Framework中,最重要的莫過於Spring Framework的Inversion Of Control(IOC)控制反轉。本文會先介紹IOC技術,以後是完整且全面的Spring Framework Aspect-Oriented Programming(AOP)面向切面編程。從概念上說,Spring的AOP是很好理解的,同時它也能夠解決企業編程的80%的AOP問題。

Spring整合了AspectJ(一個當前特性最豐富,最成熟的Java企業級AOP實現)。(暗示Spring的AOP很牛逼)

IOC容器

Spring IOC容器和Bean介紹

IOC也做爲Dependency Injection(DI)依賴注入而被人知曉。它是一個定義對象依賴的過程,對象從構造參數,工廠方法參數,屬性值獲取建立此對象須要的依賴(其實就是其餘對象)。

容器就有點秀了!它在對象須要依賴時(須要其餘對象時)主動地注入進去,這個過程本質上經過直接構造類或者相似服務定位模式的機制來取代bean實現對於依賴的實例化或定位過程,「接管了」對象對於依賴的控制,就像「反轉同樣」,因此叫控制反轉(Inversion of Control)。

org.springframework.beans和org.springframework.context這兩個包是Spring Framework IOC容器的基礎包,其中:BeanFactory接口提供了一種高級配置機制,這種機制能夠管理任何類型的對象。ApplicationContext是它的一個子接口,它擁有:

a)更簡單地整合Spring AOP特性。
b)管理資源處理。
c)發佈事件。
d)應用層次的特殊上下文(context),好比在Web應用中的WebApplicationContext。

簡而言之,BeanFactory提供了配置框架和基礎功能,ApplicationContext添加一些企業開發方面的功能。Application是BeanFactory的一個徹底的超集,本章僅僅用它來描述Spring IOC容器,想要了解更多關於BeanFactory,詳見這裏

在Spring裏,構成你應用骨幹的對象實例被稱爲「bean」,bean是啥呢?是一個實例化了的,組裝了的,同時被Spring IOC容器管理的對象。bean僅僅是你應用中的衆多對象之一。全部的Bean以及他們所使用的依賴,都被容器使用配置元數據利用反射管理着。

容器概覽

org.springframework.ApplicationContext接口表明了Spring IOC容器,它負責實例化,配置和裝配bean。容器經過讀取配置元數據來決定對哪一個對象進行實例化,配置,和裝配。元數據以XML, Java註解或Java代碼形式。它讓你能夠清楚地指明構成你應用的對象,以及它們之間豐富的內部依賴關係。

下圖展現了Spring是怎麼工做的:

![](https://user-gold-cdn.xitu.io/2020/3/21/170fb8fe8facbbda?w=498&h=296&f=png&s=3788)

配置元數據

正如圖所示的那樣,Spring IOC容器使用一個配置元數據構成的表單。這個含有配置元數據的表單,表明着你,也就是此應用的開發者,但願Spring容器應該怎樣實例化,配置和裝配你應用中的Bean組件。

傳統的設置配置元數據的方法是使用簡單直觀明瞭的XML形式,這也是本章主要的配置方法。

P.s.
XML並非惟一的格式,Spring IOC容器已經從之解耦出來了,當下,愈來愈多的開發者使用基於Java註解的配置方法(好比我)。

想要了解更對其餘配置格式?

*基於註解的配置(Spring 2.5引入)

*基於Java的配置(Spring 3.0引入,一些由Spring JavaConfig提供的特性逐漸成了Spring Framework的核心,因此你可使用Java配置爲你的應用提供外部Bean而不是使用XML文件,想使用這些新特性?試試@Configuration, @Bean, @Import, @DependsOn)

Spring配置考慮到容器至少管理一個(一般不少個)Bean定義,Java配置在@Configuration註解修飾的類裏面使用@Bean註解來完成定義這些Bean。

這些Bean定義與實例對象聯繫起來而後修飾你的應用。一般狀況下,你會定義業務層對象,好比數據庫訪問對象(DAO. Data Access Object),表示對象,例如Struts Action實例,基礎結構對象,例如Hibernate SessionFactories,JMS隊列,諸如此類。通常來講,加載域對象是DAO或業務邏輯的職責,因此容器裏面不會有細粒化的域對象。不過,你可使用Spring對於AspectJ的整合來配置IOC容器控制以外建立的對象。詳見

簡單用法:

public class Book
{
    // ...
}

@Configuration
public class WebConfig
{
    // ...
    @Bean(name = "book")
    public Book book()
    {
        return new Book();
    }
}

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>

若須要在某個配置類引用其餘的配置類,能夠在類前面加上:

@Import(value = {ConfigA.class, ConfigB.class})

或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>

使用容器

經過使用:

T getBean(String name, Class<T> requiredType)

方法,能夠獲取Bean實例。

可是,理想狀況下,你不該該使用getBean()方法,由於這會破壞容器對於對象的自動管理,你可使用諸如@Autowired的註解來在特殊Bean裏面獲取你須要的依賴。(說白了,別有事沒事調用getBean(),測試除外,那否則,你還有IOC容器幫你管理幹啥?直接本身整唄!弟弟!)

Bean概覽

在IOC容器裏面,Bean的定義以BeanDefinition的形式呈現。它們包含如下的元數據:

a)包限定名的類,包含了Bean的實現類。
b)Bean行爲定義組件,定義了Bean在IOC容器裏面的行爲。
c)對於此Bean須要的其餘Bean的引用,這些引用又被稱爲「合做者」或「依賴」。
d)其餘用於設置新建立的Bean的配置,好比超時時間,數據庫鏈接池數量等。

元數據被傳遞到一個配置集合中,以用來表達Bean定義。

具體的Bean配置集合,詳見

ApplicationContext還容許用戶在容器外面自定義Bean。經過ApplicationContext的getBeanFactory()方法獲取,此方法返回BeanFactory的DefaultListableBeanFactory實現,DefaultListableBeanFactory支持經過registerSingleton(..)和registerBeanDefinition(..)來註冊Bean。不過,一般狀況下,應用只能和經過配置元數據定義的Bean協做。

命名Bean

一個Bean一般含有一個或多個惟一標識符。若是不指出bean名字,就會使用默認名(類名首字母小寫),bean命名遵照Java命名規範,首字母小寫+駝峯命名。固然還可使用別名(說真的,通常沒用過)。

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

實例化Bean

對於XML,應該指出(強制地)其類型。在這種方式下,能夠經過調用類的構造器建立,等同於new一個實例;還能夠調用靜態的工廠方法來建立。使用構造器建立時,容器會本身尋找構造器,因此你就只須要像編寫普通類那樣就好,這裏指出,最好有一個無參構造器。

比方說:

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

Spring容器不只僅能夠管理實體類,虛擬類也能夠被其管理,好比,不符合Java Bean規範的數據庫鏈接池。

對於XML配置,使用靜態工廠方法時,class指出工廠類,factory-method指出具體的靜態方法。如果使用實例工廠方法,把class屬性換成factory-bean並指向工廠類就好。

例如:

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

實例工廠方法:

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

依賴

典型的企業級應用一般不會包含單例對象,即便是最小的應用,它的某個對象也須要別的對象的嵌入才能運行。本節介紹如何經過定義Bean配置來實現把單個的對象組織起來,實現徹底解耦合的應用。

依賴注入

Dependency Injection依賴注入(DI)指的是這樣的一個過程:對象只能經過構造器參數,工廠方法參數,在對象實例被構造以後的屬性值或工廠方法的返回值來定義它們的依賴。容器在建立這些Bean時主動注入這些依賴,這個過程稱爲對於Bean本身控制依賴的獲取,定位這一職責的反轉。

歸功於DI的存在,代碼更加簡潔明瞭,解耦更加高效。Bean不在主動引入他們須要的依賴,因此測試更加容易。介於某些Bean是接口的實現類,因此抹除某些特性並加入新的來測試,模擬都變得那麼簡單!

  1. 基於構造器的DI

基於構造器的DI,由容器調用無參或有參構造器完成。調用靜態工廠方法也能夠起到相同的效果,如下展現一個構造器注入的例子:

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on a MovieFinder
    private MovieFinder movieFinder;

    // a constructor so that the Spring container can inject a MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}

XML格式

<beans>
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg ref="beanTwo"/>
        <constructor-arg ref="beanThree"/>
    </bean>

    <bean id="beanTwo" class="x.y.ThingTwo"/>
    <bean id="beanThree" class="x.y.ThingThree"/>
</beans>

類型匹配:

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

順序匹配:

<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>
  1. 解析構造參數

這玩意屬於XML的了,說白了就是,構造器參數匹配順序出現歧義時,應顯式地指明順序。在Java Configuration裏面,直接@Autowired就完事了,整這麼多記不住的!

  1. 基於setter的DI

容器在調用過你的無參構造器或無參靜態工廠方法後,調用你的setter方法完成依賴注入。來個例子看看:

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on the MovieFinder
    private MovieFinder movieFinder;

    // a setter method so that the Spring container can inject a MovieFinder
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}

除了以上兩種注入,ApplicationContext還支持在構造器注入以後再次經過調用setter進行注入。

構造器注入和setter注入比較:

在setter方法上使用@Required註解可使此屬性成爲必須注入的依賴。帶有參數驗證的控制器注入是更好的選擇。

Spring 團隊建議使用構造器注入,這樣能夠更大程度上減小組件構建時參數變化帶來的風險,以及非空斷定。長遠角度來講,此方法構建的Bean一般是初始化狀態的完美Bean。過多的構造參數,可能說明你的代碼寫得太垃圾了,重構吧!少年!

setter方法注入,通常適用於可選依賴,這種依賴通常能夠被指定一個默認值,可是,仍是得注意對於它的非空檢查。setter注入的好處在於,後期能夠再次配置或注入依賴,增長了變通性。

使用依賴注入,可讓第三方對象也更加方便的享受到容器管理的便捷,可是一旦第三方類沒有setter方法,就只能經過構造器注入依賴了。

  1. 依賴解析過程

容器進行依賴解析過程以下:

a)經過配置元數據(好比xml文件,Java代碼,註解)來建立和初始化ApplicationContext。
b)對於每一個Bean,它的依賴項都以屬性,構造器參數,靜態工廠方法參數呈現出來,當Bean被建立時,依賴會自動添加進去。
c)每個屬性或構造器參數,都是容器裏面的一個對於其餘Bean的引用,或準備設置的值的實際定義。
d)每個屬性或構造器參數的值,都是從特殊類型轉換到屬性或構造器參數的實際類型以後的值。默認狀況下,Spring能夠把String類型的值轉換成八大基本類型。

在容器建立時,Spring會驗證每一個Bean配置的合法性,Bean屬性在Bean被建立以前,一直不會被設置。單例和預建立的Bean會在容器建立時一同被建立,其他狀況均爲須要建立時纔會建立,由於每次建立都會引起連鎖反應(Bean的依賴被建立,以來的依賴被建立...),因此對於不匹配的解析可能會在稍後出現。

注意避免循環依賴,好比A依賴B,B依賴C,C依賴A。對於這個種狀況,要注意避免,好比經過基於setter的DI來下降依賴。

Spring容器會在加載期間推斷潛在的問題(好比空指針,循環引用)。Spring儘量遲地設置屬性,這代表,可能一開始建立Bean正常,但過會就會拋異常。這也就是爲何ApplicationContext使用預建立做爲默認方式的緣由。在ApplicationContext建立時,以時間和內存換取更加可靠地系統(發現更多潛在的問題),而不是在須要建立時才發現問題,正是Spring採起的方式。固然,你能夠覆寫這個策略,實現單例建立(儘量遲地建立)。

若是啥問題也麼得,Spring就會開始注入Bean了,此時Spring會提早配置好須要建立的Bean所須要的依賴。這意味着,若是A須要B,那麼Spring可能會在初始化A以後,而後配置B,再而後調用A的setter方法來完成DI。換句話說,一個Bean被建立,而後調用它的setter方法注入依賴,而後相關的生命週期方法會被調用。

  1. 依賴注入的例子
<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

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

依賴和細節配置

正如前面提到的那樣,你能夠經過引用其餘被容器管理着的Bean會內置的值來實現對屬性或構造器參數的設置。

  1. 直接值

就,字面意思,寫在屬性標籤(<property/>)裏面的字符串,可做爲屬性值進行注入,這屬於XML文件了,典型的用法就是MyBatis數據庫配置。

<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-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
    https://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.context.support.PropertySourcesPlaceholderConfigurer">

    <!-- 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="theTargetBean" class="..."/>

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

等同於下面這個(運行時)

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

<bean id="client" class="...">
    <property name="targetName" value="theTargetBean"/>
</bean>
  1. 引用其餘的Bean

也是字面意思`,經過引用此容器管理的其餘Bean來設置當前Bean的屬性。

實例:

<bean id="accountService" <!-- bean name is the same as the parent bean -->
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
    </property>
    <!-- insert other configuration and dependencies as required here -->
</bean>
  1. 內部Bean

爲了建立此Bean而建立的一個匿名Bean,就像在類裏面建立了匿名類同樣;沒法被容器管理,訪問,注入,只是一個生成此Bean的輔助Bean。

實例:

<bean id="outer" class="...">
    <!-- instead of using a reference to a target bean, simply define the target bean inline -->
    <property name="target">
        <bean class="com.example.Person"> <!-- this is the inner bean -->
            <property name="name" value="Fiona Apple"/>
            <property name="age" value="25"/>
        </bean>
    </property>
</bean>
  1. 集合

集合標籤可用於設置Bean的集合類型,常見的集合都可使用。

看一個例子:

<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- results in a setAdminEmails(java.util.Properties) call -->
    <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>
    <!-- results in a setSomeList(java.util.List) call -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- results in a setSomeMap(java.util.Map) call -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key ="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- results in a setSomeSet(java.util.Set) call -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>

對於集合合併

<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>
  1. Null和Empty的String值

空字符串就是屬性值爲"",若不設置屬性,屬性就爲Null。

  1. 複合屬性名

就也是字面意思,至關於屬性的屬性,給屬性的屬性設值。

好比:

<bean id="something" class="things.ThingOne">
    <property name="fred.bob.sammy" value="123" />
</bean>

使用depends-on

能夠強制此Bean所使用的依賴在初始化此Bean錢被強制初始化。

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

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

是一個屬性,指出是否延遲初始化。

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

容器級別的延遲:

<beans default-lazy-init="true">
    <!-- no beans will be pre-instantiated... -->
</beans>

自動織入協做器

經過設置<bean/>的autowire屬性,便可設置自動織入模式。
自動織入的優勢:

a)大大減小指定屬性或構造器參數的須要。
b)當你的應用更新時,自動織入能夠更新配置。這個特性,在開發方面很是有用。

自動織入有四個模式

a)no: 不開啓自動織入,經過引用其餘Bean實現引用依賴。
b)byName: 根據屬性名自動織入
c)byType: 若是容器存在此屬性類型,便織入。
d)constructor: 相似byType,可是隻適用於構造器參數。

自動織入的侷限性和不足:

暫略不表。

去除自動織入中的Bean

暫略不表。

<span id="method-injection">方法注入<span/>

背景:大多時的場景,大多數的Bean都是單例的,當一個單例Bean須要和另外一個單例Bean協做時,或一個非單例Bean須要和另外一個非單例Bean合做時,一般你會經過把某個Bean定義成另外一個(Bean)的屬性來處理依賴問題。當Bean生命週期不一致時,嚯嚯,問題來了。假設在A的每一個方法調用上,單例Bean A要使用非單例Bean B。容器只會建立A的實例一次,因此只有一次設置屬性B的機會,可是容器可不能在每次須要B的時候都提供一個新的實例啊!這就形成了問題。

解決措施之一是,暫時取消一些IOC,你能夠經過實現ApplicationContextAware接口來讓A發現容器,並經過調用容器的getBean("B")方法來在每次須要B實例時,獲取一個新的實例。來看一個例子:

// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

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

以上代碼耦合到了Spring框架,因此不是很推薦,可是這種方法確實可讓你寫出乾淨的處理代碼。

  1. 查找方法注入

查找方法注入是容器使用容器所管理的另外一個Bean的方法來來重寫當前Bean的某個方法的能力。查找一般調用原型Bean,Spring經過CGLIB的動態代理來動態地生成子類,藉此實現這個方法的注入。

或者這麼說(我的理解),對於抽象類的某個方法,沒有實現,Spring會使用動態代理來覆寫或添加其子類的方式來實現對於此抽象類的方法的實現,@Lookup就是告訴Spring,使用哪一個類的同名方法來實現。

假設存在以下須要代理的類:

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

被代理的方法,應該具備如下方法簽名:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

對於XML形式,有以下方法:

<bean id="myCommand" 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="myCommand"/>
</bean>

對於註解形式,能夠這麼聲明查找方法:

public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup("myCommand")
    protected abstract Command createCommand();
}

或這樣,一種更加簡潔的方法,經過返回值查找Bean:

public abstract class CommandManager {

    public Object process(Object commandState) {
        MyCommand command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup
    protected abstract MyCommand createCommand();
}
  1. 任意方法替換

另外一種稍不如查找方法的使用方式,是用另外一個方法實現,替換容器裏面的Bean的任意方法。能夠先跳過,待到往後須要再來看。
考慮以下類:

public class MyValueCalculator {

    public String computeValue(String input) {
        // some real code...
    }

    // some other methods...
}

而後有一個類,提供了此方法的新的實現,以下所示:

public class ReplacementComputeValue implements MethodReplacer {

    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
        // get the input value, work with it, and return a computed result
        String input = (String) args[0];
        ...
        return ...;
    }
}

多是經過類名來肯定惟一替換的...

類名格式:Replacement+須要替換的方法名+implement MethodReplacer。

XML以下所示:

<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
    <!-- arbitrary method replacement -->
    <replaced-method name="computeValue" replacer="replacementComputeValue">
        <arg-type>String</arg-type>
    </replaced-method>
</bean>

<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>

Bean做用域

當你建立一個Bean定義時,至關於你也建立了一個基於此Bean定義的規則,此規則指出瞭如何建立類的實例,是單例仍是原型Bean。這是一個重要的思想,介於此,你能夠爲某個類建立多個基於同一建立規則的對象實例。

你不只能控制注入到從一個Bean定義所建立的對象的各類依賴和配置的值;你還能夠控制從一個確切的Bean定義所建立的對象的做用域。這種方法是強有力且靈活的,由於你能夠經過配置來選定對象的做用域而不是在Java類級別上從新選定對象做用域。Bean能夠用於其中之一的做用域,Spring Framework擁有六中做用域,只有當你編寫Web-Aware ApplicationContext時,後四個纔可用。固然,亦可自定義做用域。

看一個描述表格:

做用域 描述
單例(singleton) 對於每一個IOC容器的全部同一Bean的引用,都共享同一個Bean實例。
原型(prototype) 對於每個引用,都建立一個新的實例。
請求(request) 每個HTTP請求都有本身的實例對象引用。
會話(session) 把範圍限定爲一個HTTP Session生命週期內。
應用(application) 把範圍限定爲ServletContext生命週期內
WebSocket(websocket) 把範圍限定爲WebSocket聲明週期內。
線程(thread) 僅限定Spring 3.0+,參見詳見

單例Bean

咳咳,啥叫單例Bean呢?就是在容器裏面,只有一個被共享的Bean實例被容器管理着,這種狀況就叫"單例Bean"。全部對於此Bean的引用,請求,都會獲得同一個實例。

當你定義一個單例Bean時,Spring IOC容器只會根據Bean定義,建立一個此Bean的實例,而後緩存在單例Bean緩存池裏面(裏面全是單例的Bean),而後全部對於此Bean的請求所有都會獲得此單例Bean的引用。

...官方文怎麼說的那麼癟嘴呢?(大霧?)。說白了,就是全部的Bean對象引用共享一個實例;不一樣於new那種,每一個對象都能有本身的實例;全局變量懂吧?你們都用它的那種!那就是單例Bean。

來個圖康康

對於基於註解的配置,以下使用方法:

@Scpoe(ConfigurableListableBeanFactory.XXX)
@Component
public class ClassA
{
    // ...
}

XML用法:

<bean id="accountService" class="com.something.DefaultAccountService"/>

<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>

原型Bean

原型Bean不一樣於單例Bean,容器會爲每一個請求都建立一個實例對象,你應該知道,對於有狀態的Bean,應該使用原型Bean,對於無狀態的Bean,建議使用單例Bean。

來張圖康康:

那啥,DAO組件一般應定義爲單例的,由於它不具有對話狀態。

來個原型Bean設置樣例看看:

<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>

不一樣於單例的Bean,原型Bean僅由容器建立,而容器並不負責其完整的生命週期,因此,原型Bean的資源釋放,要手動處理。或者經過使用自定義的bean post-processor。

在某種程度上來講,Spring 容器之於原型Bean就像Java的new同樣,除了建立以外的全部生命週期方法應該由編寫者處理。

含有原型Bean依賴的單例Bean

假若某個單例Bean須要原型Bean做爲依賴,請記住,對於依賴的每次注入,都是一個全新的實例,不一樣屬性之間無法共享同一個依賴實例,若是想共享,那就不妨考慮一下方法注入

Request, Session, Application和WebSocket做用域

在使用以前,一般須要作一些初始化步驟,可是並非必須的。若是你用的是Spring MVC的話,DispatcherServlet會幫你作足準備工做,某些老舊的Servlet容器可能須要你本身配置某些起步須要。

  1. 請求域

先說怎麼建立一個請求域的Bean

<bean id="loginAction" class="com.something.LoginAction" scope="request"/>

或Java配置

@RequestScope
@Component
public class LoginAction {
    // ...
}

Request類型的Bean生命週期僅爲當前request請求,請求結束被建立,請求完成被銷燬。

  1. 會話域

建立:

<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>

Java配置

@SessionScope
@Component
public class UserPreferences {
    // ...
}

生命週期同一個HTTP Session一致,會話被建立,此類Bean也會被建立,會話被銷燬,此類Bean也會被銷燬。

  1. 應用域

建立:

<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>

Java配置:

@ApplicationScope
@Component
public class AppPreferences {
    // ...
}

相似於單例Bean,由於整個Web應用都共享同一個實例,這個Bean是做爲ServletContext的一個屬性來設置的,因此會一致存在於整個Web中。同單例Bean的區別在於:它是屬於ServletContext而不是ApplicationContext的;二是它是做爲ServletContext屬性來使用的。

  1. 使用被限定的Bean做爲依賴。

對於使用HTTP-Request範圍的Bean做爲依賴注入到一個長生命週期的Bean中去,可能須要代理,一種方法是使用AOP代理。

這個用的很少,先跳過,原文連接放這

  1. 選擇要建立的代理類型

同上,先略過。

自定義做用域

  1. 建立一個自定義做用域

想把你本身的scope整合到Spring容器裏面,你須要實現org.springframework.beans.factory.config.Scope接口。查看Java Doc以便了解更多。

Scpoe接口有四個方法來獲取,移除,銷燬對象。

Object get(String name, ObjectFactory<?> objectFactory)

Object remove(String name)

void registerDestructionCallback(String name, Runnable destructionCallback)

String getConversationId()
  1. 使用一個自定義做用域

你須要把Scope註冊到Spring容器,經過ConfigurableBeanFactory的registerScope方法來註冊,而ConfigurableBeanFactory能夠經過ApplicationContext的BeanFactory屬性獲取。

使用示例:

Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);

自定義Bean屬性

Spring提供了一系列接口,用以實現自定義Bean屬性。

生命週期回調

爲了使你本身的Bean與容器互動,你要實現InitializingBean接口和DisposableBean接口,容器會在前階段調用afterPropertiesSet()方法,後階段調用destroy()方法,來使你的Bean在初始化和銷燬時產生某一行爲。

在Spring內部,Spring框架會使用BeanPostProcessor實現類來處理它所能找到的回調接口,並調用合適的方法。若是你想自定義某個Spring沒有默認提供的行爲,經過實現BeanPostProcessor便可,詳細信息在這裏

爲了初始化和銷燬回調,Spring管理着的對象可能還須要實現Lifecycle接口,以致於這些對象能夠進行實際上的「開啓」和「關閉」操做,正如被容器本身的生命週期所驅動着那樣(言外之意,和容器生命週期一致)。

  1. 初始化回調

org.springframework.beans.factory.InitializingBean接口讓Bean在容器爲其添加所有依賴後擁有初始化行爲的能力。方法聲明以下:

void afterPropertiesSet() throws Exception;

可是咱們不建議你使用此接口,由於這會把代碼耦合到Spring。咱們推薦使用@PostConstruct註解,或者爲POJO指定一個初始化方法。指定初始化方法樣例:

XML配置

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>

Java配置

public class BeanOne {

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

public class BeanTwo {

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

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

注意,初始化方法,應該是一個無參方法。

以上方法等效於下面這個:

public class AnotherExampleBean implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
        // do some initialization work
    }
}
  1. 銷燬回調

org.springframework.beans.factory.DisposableBean接口提供了在Bean被銷燬時能夠採起的措施,方法聲明以下:

void destroy() throws Exception;

一樣,Spring不建議使用此接口,而是使用@PreDestroy註解或指定銷燬方法。先上樣例看看:

<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>

Java配置

public class BeanOne {

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

public class BeanTwo {

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

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

銷燬方法應該也是無參的。

以上方法等同於下面這個:

public class AnotherExampleBean implements DisposableBean {

    @Override
    public void destroy() {
        // do some destruction work (like releasing pooled connections)
    }
}
  1. 默認初始化和銷燬方法

除了經過指定初始化和銷燬方法外,Spring還提供按名字查找的方式來主動地調用初始化或銷燬方法,這就是默認狀況。默認按照名字來查找並調用,可是這些默認方法,名字必須符合規範,好比初始化方法叫"init()",銷燬方法叫"destroy()",此時它們就會被當作默認方法,供Spring容器調用。

好比:

public class DefaultBlogService implements BlogService {

    private BlogDao blogDao;

    public void setBlogDao(BlogDao blogDao) {
        this.blogDao = blogDao;
    }

    // this is (unsurprisingly) the initialization callback method
    public void init() {
        if (this.blogDao == null) {
            throw new IllegalStateException("The [blogDao] property must be set.");
        }
    }
}

對於XML格式

<beans default-init-method="init">

    <bean id="blogService" class="com.something.DefaultBlogService">
        <property name="blogDao" ref="blogDao" />
    </bean>

</beans>

默認方法應聲明在頂級<beans/>標籤裏,固然,也能夠經過在<bean/>標籤指定初始化或銷燬方法來覆寫默認的方法。

  1. 組合生命週期機制

Spring提供了多種機制,如今是時候去總結他們了。

能夠產生初始化行爲的操做:

a)用@PostConstruct註解標註的方法
b)InitializingBean接口的afterPropertiesSet()方法
c)自定義的init()方法

能夠產生銷燬行爲的操做:

a)@PreDestroy註解標註的方法
b)DisposableBean接口的destroy()方法
c)自定義的destroy()方法
  1. 開啓和關閉(生命週期)回調

Lifecycle接口爲任何擁有本身的生命週期需求的對象定義了幾個潛在的方法。看看接口:

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

任何由Spring管理的對象都有可能實現此接口,當ApplicationContext本身被啓動或銷燬時,它會按層級結構似的觸發它所管理的Bean的啓動(start),關閉(stop)方法。固然,它是經過把此過程代理給LifecycleProcessor接口來實現的,看定義:

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

此接口除了繼承自Lifecycle的三個方法以外,還添加了兩個本身的方法,用於當ApplicationContext刷新和關閉時也對其所管理的Bean執行相同的操做。

注意:Lifecycle是一個普通的協議,僅適用於顯式地啓動與中止,可是不適合刷新階段的自啓動,若是想實現自啓動,考慮org.springframework.context.SmartLifecycle接口。固然,你也得知道中止信號並不保證會在銷燬方法調用時通知到位。在普通中止階段,中止信號會先於銷燬方法傳遞給相關的Bean,可是在熱刷新或終止刷新嘗試階段,只有銷燬方法會被調用。

關閉和啓動的順序尤其重要(廢話),某個Bean應該在它的依賴被啓動後才能啓動,並應該先於它的依賴執行關閉操做。可是不少時候,僅能根據類型進行判斷前後順序,這是不夠的,咱們須要更加細粒化的方法,org.springframework.context.SmartLifecycle接口繼承自Phased接口提供了這樣的選擇。看看代碼:

public interface Phased {

    int getPhase();
}

再來看看SmartLifecycle:

public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}

當容器啓動時,擁有最小值的對象啓動最先,不過也關閉最晚。意味着,若把phase設置爲Integer.MIN_VALUE,那麼此對象會最早啓動;
當phase設置爲Integer.MAX_VALUE時,對象會最後啓動,最早關閉。

由SmartLifecycle接口定義的中止方法會接收一個回調,任何實現了此接口的類必須在關閉過程結束了後,調用回調方法的run()方法。它能夠保證在必要的時候進行異步關閉,由於LifecycleProcessor默認實現類DefaultLifecycleProcessor會在每一個階段等待超時時間到來以進行調用那個回調。默認時限是30s,固然,你能夠本身修改此時間。你能夠經過實現名字爲"lifecycleProcessor"的Bean來實現對LifecycleProcessor的覆寫,固然,這種方式能夠設置超時時間。

對於LifecycleProcessor接口,他也一樣定義了對於ApplicationContext的刷新和關閉操做。後者只有在stop()被顯式地調用時纔會執行關閉操做;但一樣會發生在ApplicationContext被關閉時。當ApplicationContext被刷新時,默認lifecycle處理器會檢查每一個SmartLifecycle對象的isAutoStartup()返回值以斷定是否對其進行重啓操做。phase屬性和depends-on所定義的依賴,會根據前面所闡述的規則,來判斷啓動順序。

  1. 優雅地在非web應用中關閉Spring IOC容器

若是你使用的是非web應用的話,記得綁定一個關閉操做到JVM上,這樣能夠釋放單例Bean所佔有的資源。經過把關閉操做註冊到JVM上,便可實現「優雅地」關閉應用,用法以下:

調用ConfigurableApplicationContext的registerShutdownHook()方法。

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

        // add a shutdown hook for the above context...
        ctx.registerShutdownHook();

        // app runs here...

        // main method exits, hook is called prior to the app shutting down...
    }
}

<span id="applicationContextAndBeanNameAware">ApplicationContextAware和BeanNameAware</span>

當ApplicationContext建立了一個實現了org.springframework.context.ApplicationContextAware接口的實例對象,這個實例對象就得到了一個指向ApplicationContext的引用,以下是此接口的定義:

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

所以,Bean能夠程序化的人爲管理ApplicationContext,經過把ApplicationContext接口強制轉換到此接口的子類,能夠得到更多的用法。其中一個典型的用法莫過於獲取其餘Bean。有時此功能是有用的,可是你應該避免使用它,由於它破壞了IOC的特性。ApplicationCOntext的其餘方法提供了諸如訪問文件資源,發佈應用事件,訪問消息資源的能力,詳見更多用法,參考這裏

自動織入是另外一個獲取ApplicationContext的方法,傳統的方法是使用基於類型對ApplicationContext進行注入到構造器或setter方法裏面。一種更加靈活的作法是,使用自動織入特性對域或方法參數進行織入。這是使用@Autowired註解進行織入的方法,也是我喜歡的方式。

當一個Bean實現org.springframework.beans.factory.BeanNameAware接口時,就被提供了一個能夠修改它的Bean定義中Bean名字的方法,以下描述:

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

其餘Aware接口

除了ApplicationContextAware和BeanNameAware以外,Spring還提供了一系列Aware接口供Bean使用,來向容器代表他們所須要的某些基礎依賴。

看個表:

(原文不太好翻譯,爲了防止產生歧義,直接放原文)

Name Injected Dependency Explained in…
ApplicationContextAware Declaring ApplicationContext. ApplicationContextAware and BeanNameAware
ApplicationEventPublisherAware Event publisher of the enclosing ApplicationContext. Additional Capabilities of the ApplicationContext
BeanClassLoaderAware Class loader used to load the bean classes. Instantiating Beans
BeanFactoryAware Declaring BeanFactory. ApplicationContextAware and BeanNameAware
BeanNameAware Name of the declaring bean. ApplicationContextAware and BeanNameAware
BootstrapContextAware Resource adapter BootstrapContext the container runs in. Typically available only in JCA-aware ApplicationContext instances. JCA CCI
LoadTimeWeaverAware Defined weaver for processing class definition at load time. Load-time Weaving with AspectJ in the Spring Framework
MessageSourceAware Configured strategy for resolving messages (with support for parametrization and internationalization). Additional Capabilities of the ApplicationContext
NotificationPublisherAware Spring JMX notification publisher. Notifications
ResourceLoaderAware Configured loader for low-level access to resources. Resources
ServletConfigAware Current ServletConfig the container runs in. Valid only in a web-aware Spring ApplicationContext. Spring MVC
ServletContextAware Current ServletContext the container runs in. Valid only in a web-aware Spring ApplicationContext. Spring MVC

Bean定義的繼承原則

一個Bean定義能夠包含許多配置信息,包括構造器參數,屬性值,和特定於容器的信息,好比初始化方法,靜態工廠方法名,諸如此類。一個子Bean定義(不是子Bean,定義,而是子,Bean定義;請注意斷句)會繼承來自父Bean定義的配置元數據。子定義能夠覆寫某些屬性值,或者按需添加新的定義。使用父子Bean定義能夠省下好多重複的編寫。實際上,這是模板的一種形式。

若是程序化地使用ApplicationContext的話,子Bean定義由ChildBeanDefinition呈現。若是是XML形式的,看這裏:

<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"/>
    <!-- the age property value of 1 will be inherited from parent -->
</bean>

若是未指定子定義,那麼子Bean定義會使用父Bean定義,可是也依舊能夠覆寫它,後者這種狀況,子Bean類必須與父Bean類兼容。

子Bean定義會繼承做用域,構造器參數值,屬性值,和方法替換,同時擁有添加新值的能力。任何你指定的域,初始化方法,銷燬方法,或靜態工廠方法設置,都會覆寫父定義中與之對應的設置。

下面的例子顯式地把父類聲明成了抽象的,若子定義沒有顯式地覆寫這一設置,那麼子定義生成的Bean也會是抽象的

<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 will inherit the value of 1 from the parent bean definition-->
</bean>

通常來講,把一個類聲明爲抽象的,就是爲了做爲模板,讓它的子定義來覆寫,任何形式的對於此抽象父定義定義的Bean的引用都會獲得一個錯誤。

P.s.我的理解,父Bean定義就像抽象相似的,做爲模板規範了子Bean定義,自身無法實例化,只能實例化它的子Bean定義定義的Bean。

注意:對於任何你只想做爲模板來使用的定義,必定聲明爲抽象的(abstract),否則IOC容器會提早把他們實例化了。

容器的延展知識

一般,開發人員不須要專門編寫ApplicationContext實現類的子類,經過注入幾個特殊的組合接口的實現類就好。

經過BeanPostProcessor自定義Bean

BeanPostProcessor提供了回調方法,實現它們,你能夠提供你本身的初始化邏輯。若是你想在IOC容器實例化,配置或初始化Bean以後實現一些自定義邏輯,你能夠注入一個或多個BeanPostProcessor實現類。

看一下BeanPostProcessor的定義:

public interface BeanPostProcessor {
    // Apply this BeanPostProcessor to the given new bean instance after any bean initialization callbacks (like InitializingBean's afterPropertiesSet or a custom init-method).
    default Object postProcessBeforeInitialization(Object bean, String beanName)
    // Apply this BeanPostProcessor to the given new bean instance before any bean initialization callbacks (like InitializingBean's afterPropertiesSet or a custom init-method).
    default Object postProcessBeforeInitialization(Object bean, String beanName)
}

你也能夠定義多個BeanPostProcessor,並經過設置order順序來控制它們被執行的順序。只有在BeanPostProcessor實現了Ordered接口後才能夠設置order屬性。因此,若是你實現本身的BeanPostProcessor接口的話,不妨順便實現Ordered接口。詳細信息見JavaDoc

注意:
a)BeanPostProcessor實例用來操做Bean實例,這代表,Bean建立由Spring IOC容器負責,而後剩餘的工做由BeanPostProcessor完成。
b)BeanPostProcessor只能做用域一個容器或這個容器的子容器裏,即便是繼承同一容器的不一樣子容器,也無法共享BeanPostProcessor。
c)經過使用BeanFactoryPostProcessor來更改實際的Bean定義。

org.springframework.beans.factory.config.BeanPostProcessor實際上只包含兩個回調方法,當這樣的一個類被註冊到了容器後,對於容器裏面的每一個Bean,post-processor(後處理器)會在容器初始化方法(好比,InitializingBean.afterPropertiesSet(),或者任何被聲明的init()方法)被調用以前和任何Bean初始化回調以後會獲取到一個回調方法。post-processor(後處理器)會對Bean實例採用任何行爲,包括徹底地忽略回調。一個Bean後處理器一般會檢查回調接口,或者會經過一個代理來包裝一個Bean。某些Spring AOP基礎類使用後處理器的方式來提供代理邏輯。

ApplicationContext會自動推斷實現了BeanPostProcessor接口的,且定義在配置元數據裏面的Bean,這些Bean會被註冊到容器裏面,當Bean建立時,它們會被調用,BeanPostProcessor能夠像普通Bean那樣部署在容器裏面。

當你使用@Bean註解在配置類裏面聲明BeanPostProcessor時,記得把返回值設置成這個類自身或,至少爲BeanPostProcessor接口,顯式地代表這個Bean的"後處理器屬性"。不然,在徹底建立它以前,ApplicationContext無法經過類型識別來推斷出BeanPostProcessor。因爲BeanPostProcessor須要被更早地實例化以用來處理其餘的Bean的實例化,因此這點注意事項是很重要的。

注意:
程序化地註冊BeanPostProcessor。雖然咱們推薦的註冊方式是經過ApplicationContext自動推斷,可是你也可使用ConfigurableBeanFactory的addBeanPostProcessor方法來完成註冊。這種方式在你須要評估註冊前的條件環境邏輯和在繼承的容器之間複製Bean後處理器顯得十分好用。可是這種方式無法指定執行順序,還有就是,此方式註冊的後處理器要先於自動推斷註冊的處理器執行。

實現BeanPostProcessor接口的類是特殊的,而且容器對它們的處理方式有所不一樣。做爲ApplicationContext特殊啓動階段的一部分,在啓動時會實例化它們直接引用的全部BeanPostProcessor實例和Bean。接下來,以排序方式註冊全部BeanPostProcessor實例,並將其應用於容器中的全部其餘Bean。由於AOP自動代理是做爲BeanPostProcessor自己實現的,因此BeanPostProcessor實例或它們直接引用的bean都沒有資格進行自動代理,所以沒有織入的方面。

對於使用@Autowired註解注入的Bean,Spring可能會根據類型來找取,可是可能會找到某些意料以外的Bean,所以可能會讓這些Bean沒有被自動代理或後處理的資格。

接下來,來看一個使用BeanPostProcessor的例子:

package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

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

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

Spring附帶的RequiredAnnotationBeanPostProcessor在容許把註解裏面的值注入到屬性上面的同時,還容許使用後處理器的特性。

經過BeanFactoryPostProcessor自定義配置元數據

BeanFactoryPostProcessor不一樣於BeanPostProcessor的地方在於,它是在配置元數據層面進行操做的,它能夠讀取配置元數據並在IOC容器實例化除了BeanFactoryPostProcessor類型的Bean以前更改它們, 詳見

看一下定義:

public interface BeanFactoryPostProcessor {
    // Modify the application context's internal bean factory after its standard initialization.
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
}

你能夠設置多個BeanFactoryPostProcessor,固然也能夠經過設置order屬性完成對於執行順序的控制。前提同BeanPostProcessor同樣,你得實現Ordered接口。

也能夠在BeanFactoryPostProcessor中使用Bean實例,但這會致使過早地實例化,違反了標準的容器生命週期,因此不推薦。

當一個Bean工廠後處理器在ApplicationContext上下文裏聲明後。Spring會自動執行它。爲了應用對容器內配置元數據的更改,Spring提供了一系列預聲明的Bean工廠後處理器,例如:PropertyOverrideConfigurer和PropertySourcesPlaceholderConfigurer。你也可使用自定義BeanFactoryPostProcessor,例如,註冊自定義屬性編輯器。

ApplicationContext會自動推斷部署在容器裏面,並實現了BeanFactoryPostProcessor接口的Bean,它會在合適的時候把這些Bean用做Bean工廠後處理器,你也能夠像部署其餘Bean那樣部署Bean工廠後處理器。

這裏有一篇文章,更加詳細地介紹了BeanPostProcessor和BeanFactoryPostProcessor的區別和用法

經過FactoryBean自定義初始化邏輯

能夠爲自己就是工廠的對象實現org.springframework.beans.factory.FactoryBean接口

看一下定義:

public interface FactoryBean {
    // 一個能夠被設置在BeanDefinition上的屬性名,用於當沒法經過工廠Bean類來推斷出對象類型時,能夠經過這種方式識別出來。
    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
    // Return an instance (possibly shared or independent) of the object managed by this factory.
    T getObject() throws Exception;
    // Return the type of object that this FactoryBean creates, or null if not known in advance.
    Class<?> getObjectType();
    // Is the object managed by this factory a singleton? That is, will getObject() always return the same object (a reference that can be cached)?
    default boolean isSingleton() {
        return true;
    }
}

FactoryBean也是能夠對Spring IOC容器實例化邏輯進行插拔操做的一個體現。若是你編寫了複雜而不是冗餘的XML配置,你能夠考慮使用FactoryBean來進行構建或實現初始化邏輯。

Spring框架大量使用了此接口,隨贈的類就有50+。當你想獲取FactoryBean實例而不是它產生的Bean時,只要在getBean()方法的參數前面添加一個'&'連字符就行。好比,某個FactoryBean的id爲"myBean",僅需這樣作:getBean("&myBean")便可得到FactoryBean實例。

基於註解的容器配置

基於註解的配置和XML配置,究竟哪一個更好呢?Spring給出的答案是:那取決於實際使用狀況,兩者各有優缺點。

@Required

@Required註解用於設置Bean屬性的方法,例如:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Required
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

上述代表,這個屬性必須在配置期間被填充值,可能經過Bean定義時的顯式地設置它的值,或自動織入它的值。

注意:這個屬性在Spring5.1以後就不推薦使用了,對於想強制填充的值,考慮使用構造器注入或InitializingBean.afterPropertiesSet()進行注入。

使用@Autowired

此註解可被用於構造器,以下所示

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

在Spring Framework4.3以後,若是Bean定義只有一個構造器的話,能夠省略此註解,可是吧,若是不止一個,那麼須要添加此註解,以指明哪一個構造器應該被使用。

固然,@Autowired也能夠用在傳統setter方法上,例如:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

也能夠應用到任意名字的方法,以及任意數量的參數上:

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}

亦或,應用到私有域和構造器上:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

經過添加到數組形式上,可讓Spring使用ApplicationContext注入全部的此類型的Bean:

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}

對於集合,也能夠這麼用:

public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}

映射也能夠,不過映射的鍵只能是String,表明Bean名字:

public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}

@Autowired默認必須擁有能夠注入的Bean,可是設置required屬性,能夠進行"非強制"注入:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired(required = false)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

介於Spring特殊的構造器算法,@Autowired的required屬性可能會在構造器或工廠方法參數裏略有不一樣。好比,對於只有一個構造器的場景,數組,Set,Map這類,即便@Autowired默認爲強制注入,可是這幾個能夠爲空實例。對於全部依賴項都聲明在惟一的一個多參構造器裏的場景,這是可行的。

固然,也可使用JDK8的java.uti.Optional來表示"非強制"注入:

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }
}

對於Spring Framework5.0,還可使用@Nullable來表示"非強制"注入:

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }
}

此註解也可用於那些衆所周知的可解決依賴項:BeanFactory, ApplicationContext, Environment, ResourceLoader, ApplicationEventPublisher, MessageSource, 這些類的子類也會自動解析,他們都不須要特殊的啓動設置:

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

注意:@Autowired, @Inject, @Value, @Resource都無法用於BeanPostProcessor,由於BeanPostProcessor是處理這些註解的類。因此對於你本身的BeanPostProcessor和BeanFactoryPostProcessor,你必須顯式地經過XML或@Bean方法來註冊。

使用@Primary微調基於註解的自動織入

對於基於類型注入的@Autowired來講,這種方式仍是有些寬泛,因而乎,須要@Primary來指出衆多待選Bean中,究竟哪一個更合適。好比:

@Configuration
public class MovieConfiguration {

    @Bean
    // 一樣注入MovieCatalog,這個會優先注入。
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...
}

使用Qualifiers微調基於註解的自動織入

對於特定的Bean注入,還可使用@Qualifier來進行特定的注入:把某個值與Bean進行綁定。看一個例子:

public class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;

    // ...
}

對應的注入:

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

    // ...
}

對於備選匹配,通常來講,Bean名稱默認爲匹配的值,可是有時候並不適用,因此你應當本身指出某些特定的值來進行惟一的標識,好比:"main", "EMEA", "persistent"。

對於集合類型,@Qualifier能夠起到過濾器做用,比方說:從一組備選項中過濾出某些特定名稱的Bean。

對於沒有其餘解析指示符的狀況,Spring會使用注入點名稱(屬性名)與Bean名字進行匹配。

若是你只是想利用名字做爲篩選而不是那麼的在乎類型的話,能夠試試@Resource,它不一樣於@Autowired的地方是,@Autowired優先選取類型匹配,再選取名字匹配,它優先選取名字匹配,類型就顯得不那麼強制匹配了。

介於@Resource的匹配算法,對於自己就是Map或數組的Bean,使用@Resource來進行名稱匹配會是一個不錯的選擇。固然,對於@Autowired來講,確保返回類型一致或返回類型繼承了同一父類,而後用惟一標示的"value"屬性來指明就好。

在某個配置類裏面調用它的@Bean的結果給另外一個方法,屬於"自引用"的一種。這並不推薦,解決措施之一是,把@Bean方法聲明爲靜態的,以免它和配置類的生命週期混在一塊兒;或者延遲它的解析。再否則,把他們設置在不一樣的配置類裏面,儘可能不在同一個配置裏面。

@Autowired適用於私有域,構造器和多參方法,容許使用在參數階段使用惟一標識來細粒化注入。做爲對比,@Resource僅支持私有域和單參的Bean屬性setter方法。因此,若是你想注入構造器或多參方法,請務必使用@qualifier來進行惟一注入。

固然,你也能夠建立本身的qualifier註解,僅需提供@Qualifier就好:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {

    String value();
}

而後就能夠像這樣使用:

public class MovieRecommender {

    @Autowired
    @Genre("Action")
    private MovieCatalog actionCatalog;

    private MovieCatalog comedyCatalog;

    @Autowired
    public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
        this.comedyCatalog = comedyCatalog;
    }

    // ...
}

有時,不帶名字的qualifier就能夠了,他們默認以類型進行匹配。

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {

}

使用:

public class MovieRecommender {

    @Autowired
    @Offline 
    private MovieCatalog offlineCatalog;

    // ...
}

固然,還能夠設置多個屬性,在匹配時必須知足所有屬性匹配。

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {

    String genre();

    Format format();
}

public enum Format {
    VHS, DVD, BLURAY
}

用起來,也必須都得知足:

public class MovieRecommender {

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Action")
    private MovieCatalog actionVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Comedy")
    private MovieCatalog comedyVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.DVD, genre="Action")
    private MovieCatalog actionDvdCatalog;

    @Autowired
    @MovieQualifier(format=Format.BLURAY, genre="Comedy")
    private MovieCatalog comedyBluRayCatalog;

    // ...
}

使用泛型做爲自動織入的Qualifiers

你可使用泛型類型做爲惟一標識符,好比,有以下Bean定義:

@Configuration
public class MyConfiguration {

    @Bean
    public StringStore stringStore() {
        return new StringStore();
    }

    @Bean
    public IntegerStore integerStore() {
        return new IntegerStore();
    }
}

那麼能夠這麼用:

@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean

@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean

固然,泛型標識符也能夠用於List,Map實例和數組:

// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private List<Store<Integer>> s;

使用CustomAutowireConfigurer

CustomAutowireConfigurer是一個BeanFactoryPostProcessor,它可讓你註冊本身的自定義qualifier註解類型,即便他們沒有使用Spring的@Qualifier註解進行標註。來看一個用法:

<bean id="customAutowireConfigurer"
        class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
    <property name="customQualifierTypes">
        <set>
            <value>example.CustomQualifier</value>
        </set>
    </property>
</bean>

使用@Resource進行注入

Spring也支持使用@javax.annotation.Resource進行對私有域或Bean屬性setter方法進行注入。

@Resource自帶一個name屬性,默認狀況下,Spring把它解釋成Bean名字。它適用於以名字進行注入的情景:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder") 
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

若是不進行指定name屬性的值,那麼默認爲準備註入的私有域屬性的名字或setter參數的名字。且在這種狀況下,相似於@Autowired,好比:

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

上述注入,會優先尋找一個名爲:"customerPreferenceDao",若找不到,就會尋找類型爲CustomerPreferenceDao的Bean(以類型匹配來尋找)。

使用@Value

@Value一般用於注入外在屬性,也就是那些寫在配置文件裏面的屬性。看個例子:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name}") String catalog) {
        this.catalog = catalog;
    }
}

搭配下面的配置類:

@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }

配置文件以下所示:

catalog.name=MovieCatalog

Spring提供了一個寬鬆的值解析器,假若須要的值沒有被找到,屬性名就會被當成默認值進行注入。不過,你能夠自定義一個PropertySourcesPlaceholderConfigurerBean來進行更加嚴格的控制:

@Configuration
public class AppConfig {

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
       return new PropertySourcesPlaceholderConfigurer();
    }
}

注意,當聲明PropertySourcesPlaceholderConfigurerBean時,方法必須是靜態的。

上述措施確保了佔位符未被找到時的失敗,不過,也能夠經過setPlaceholderPrefix(),setPlaceholderSuffix()和setValueSeparator()來自定義佔位符。

Spring Boot提供了PropertySourcesPlaceholderConfigurer的默認實現來加載來自application.properties或application.yml文件的屬性。

Spring還提供了內置的對基本類型的轉換,同時還能夠把基於逗號','分隔符的屬性值轉換成String數組形式。

固然,還能夠經過以下方式設定默認值:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
        this.catalog = catalog;
    }
}

某個Spring的BeanPostProcessor使用內置的ConversionService來進行把屬性值轉換成須要的類型,因此,你能夠實現你本身的ConversionService來進行轉換:

@Configuration
public class AppConfig {

    @Bean
    public ConversionService conversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
        conversionService.addConverter(new MyCustomConverter());
        return conversionService;
    }
}

當@Value包含SpEL表達式時,會在運行時動態的計算屬性值:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
        this.catalog = catalog;
    }
}

SpEL包含了對於更加複雜的數據結構的支持:

@Component
public class MovieRecommender {

    private final Map<String, Integer> countOfMoviesPerCatalog;

    public MovieRecommender(
            @Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
        this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
    }
}

使用@PostConstruct和@PreDestroy

javax.annotation.PostConstruct和javax.annotation.PreDestroy能夠被CommonAnnotationBeanPostProcessor掃描到並進行生命週期的處理,以下所示:

public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // populates the movie cache upon initialization...
    }

    @PreDestroy
    public void clearMovieCache() {
        // clears the movie cache upon destruction...
    }
}

[下一篇]()

若是你以爲對你有幫助,能夠賞幾個錢子兒嗎?(用小錢錢買大開心!)

相關文章
相關標籤/搜索