Spring框架學習之高級依賴關系配置(一)

?????上篇文章我們對Spring做了初步的學習,了解了基本的依賴注入思想、學會簡單的配置bean、能夠使用Spring容器管理我們的bean實例等。但這還只是相對較淺顯的內容,本篇將介紹bean的相關更高級的配置,主要涉及內容如下:

  • 三種方式配置Bean
  • 深入理解容器中的Bean
  • 管理Bean的生命周期
  • 高級的依賴關系配置
  • 使用XML Schema簡化DTD配置
  • 使用SpEL表達式語言

一、三種方式配置Bean
?????在這之前,我們一直使用下面這種方式配置我們的bean。

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

在bean元素中指定兩個屬性,id屬性指定了該實例在容器中唯一標識,class屬性指定該實例的類型。我們也說過,Spring會使用反射技術讀取class并創建一個該類型的實例返回。這種方式配置bean相對而言較常見,但是Spring中還有其他兩種配置bean的方式,靜態工廠和實例工廠。

靜態工廠配置bean實例:
使用靜態工廠配置bean實例,在bean元素中需要指定至少兩個屬性值。

  • class:指向靜態工廠類
  • factory-method:指定用于生成bean的靜態工廠方法

下面我們定義一個靜態工廠類及其靜態工廠方法:

/*person bean*/
public class Person {
    private String name;
    private int age;
    private String address;
    //省略setter方法
}
/*靜態工廠類及靜態工廠方法*/
public class BeanStaticClass {

    public static Person getPerson(String name,int age){
        Person person = new Person();
        person.setName(name);
        person.setAge(age);
        return person;
    }
}

配置bean:

<bean id="person" class="MyPackage.BeanStaticClass" factory-method="getPerson">
    <constructor-arg value="single"/>
    <constructor-arg value="22"/>
</bean>

如果需要向工廠傳入參數,可以使用元素<constructor-arg value="" />傳入參數。最終外部從容器中獲取person實例,打印信息:

這里寫圖片描述

這里需要再說明一點的是,除了使用<constructor-arg value="" />傳入參數去初始化bean的屬性外,我們也是可以通過property元素驅動Spring再次執行person的setter方法的,例如上述未被初始化的address屬性也可以在配置bean的時候進行初始化。

<bean id="person" class="MyPackage.BeanStaticClass" factory-method="getPerson">
    <constructor-arg value="single"/>
    <constructor-arg value="22"/>
    <property name="address" value="nanjing"/>
</bean>

實例工廠配置bean實例
實例工廠生成bean實例的配置其實和靜態工廠是類似的,只不過一個調用的是靜態方法,一個調用的是實例方法而已。使用實例工廠創建bean需要配置以下屬性:

  • factory-bean:指定工廠的實例
  • factory-method:指定工廠方法

這種方式和靜態工廠方法創建bean的方式及其類似,此處不再贅述。

顯然,后兩者于前者對于配置bean實例來說是兩種截然不同的方式,一種是聲明式配置,由Spring替我們生成bean實例,另一種則是我們程序員手動的去返回bean實例,各有各的優缺點,適情況選擇。

二、深入理解容器中的bean
?????首先我們看一段配置bean的代碼片段,

<bean id="person" class="MyPackage.Person">
    <property name="name" value="single"/>
    <property name="age" value="22"/>
    <property name="address" value="nanjing"/>
</bean>

<bean id="student" class="MyPackage.Student">
    <property name="name" value="single"/>
    <property name="age" value="22"/>
    <property name="grade" value="100"/>
</bean>

我們配置兩個bean實例,但是發現這兩個bean中存在大量相同的信息。如果容器中的bean越來越多,那么這樣大范圍的重復代碼必然導致整個配置文件臃腫,煩雜。

Spring中為我們提供一種機制,讓bean于bean之間可以繼承。例如上述代碼等同于以下代碼:

<bean id="info" abstract="true">
    <property name="name" value="single"/>
    <property name="age" value="22"/>
</bean>

<bean id="person" class="MyPackage.Person" parent="info">
    <property name="address" value="nanjing"/>
</bean>

<bean id="student" class="MyPackage.Student" parent="info">
    <property name="grade" value="100"/>
</bean>

我們抽象出來一個id為info的bean,該bean中初始化屬性name和age的值,然后我們的person和age bean通過屬性parent繼承了info,那么他們的相應屬性的值將繼承自info。當然,如果父bean和子bean中對同一屬性做了初始化,結果會用子bean中的值覆蓋父bean中的值注入到具體的bean實例中。

子bean將繼承父bean的屬性值,但是有些屬性是不能被繼承的,例如:

  • scope:bean的作用域
  • depends-on:屬性依賴
  • autowire:自動裝配
  • lazy-init:延遲加載

包括abstract屬性也是不能被繼承的。這里需要對比于Java中的類繼承機制,類的繼承關系其實是一種屬性字段和方法的繼承,而bean的繼承主要是屬性及其值的繼承。一個傾向于結構上的繼承關系,一個則傾向于值上的繼承關系。

接著我們看如何根據bean的引用獲取該bean在容器中的id值,
由于某種需要,有些時候我們需要在握有bean的實例的時候,想要獲取該實例在容器中的id。Spring允許我們通過繼承一個接口:BeanNameAware,該接口中有一個方法:setBeanName(String name),這個name的值就是該bean在容器中的id。看程序:

/*person類實現了BeanNameAware 接口*/
public class Person implements BeanNameAware {
    private String personId;

    @Override
    public void setBeanName(String s) {
        this.personId = s;

    }
}

配置文件沒有變化,

Person person = (Person) context.getBean("person");
System.out.println(person.getPersonId());

輸出結果:

person

當容器創建person實例之后,它掃描該實例是否實現了接口BeanNameAware,如果實現了該接口,那么容器將自動調用該實例中的setBeanName方法,并將當前實例的id作為參數傳入,于是我們就可以保存下該實例在容器中的id。

三、Bean的生命周期
?????在Spring容器中,只有作用域為singleton的bean才會被容器追蹤,而對于作用域為prototype的bean,容器只負責將它實例化出來,并不會追蹤它何時被初始化,何時被銷毀等。Spring容器提供兩個時機供我們追蹤Bean的生命周期:

  • 注入依賴結束時
  • Bean實例被銷毀時

對于第一種方式,我們只需要在定義bean的時候為其指定 init-method屬性的值即可。該屬性的值是一個方法的名稱,容器會在注入依賴結束的時候自動調用實例中的該方法。例如:

public class Person {
    private String name;
    private int age;

    public void init(){
        System.out.println("依賴注入結束。。。");
    }
    //省略setter方法
}

配置bean:

<bean id="person" class="MyPackage.Person" init-method="init">
    <property name="name" value="single"/>
    <property name="age" value="22"/>
</bean>

這樣,當容器對person完成注入依賴的時候,就會自動調用我們為其指定的init方法。代碼比較簡單,就不貼出運行結果了。

對于第二個時機,其實也是類似,只需要配置 屬性destory-method的值即可在bean被銷毀之前調用。此處不再贅述。

四、高級的依賴關系配置
?????一直以來,我們對于依賴關系的注入,要么使用常量注入到屬性中,要么使用引用注入到容器中。相對而言,這兩種方式對屬性的注入來說,幾乎是把"死值"注入給屬性,這樣的程序靈活性必然很差,我們平常也很少使用Spring為屬性注入固定的常量值。Spring中允許我們把任意方法的返回值、類或對象的屬性值以及其他bean的引用注入給我們的屬性。

1、獲取其他bean的屬性值
我們可以通過PropertyPathFactoryBean來獲取配置在容器中的其他bean的某個屬性的值(也就是調用它的getter方法)。在配置bean的時候必須為其指定如下兩個屬性值:

  • targetObject:告訴容器需要調用那個bean實例
  • propertyPath:告訴容器需要調用那個屬性的getter方法

PropertyPathFactoryBean是Spring內置的一個特殊的bean,它可以獲取指定實例的指定getter方法的返回值。對于這個返回值,Spring將其封裝在PropertyPathFactoryBean類型的bean實例中,我們可以選擇直接將該bean用于賦值,或者將其定義成新的bean保存在容器中。例如:

public class Person {
    private String name;
    private int age;
    //省略setter方法
}
public class Student{
    private String name;
    private int age;
    //省略setter方法
}

下面我們給出配置bean的代碼段,對于person中age我們調用Student實例中getage作為注入值。

<bean id="student" class="MyPackage.Student">
    <property name="name" value="single"/>
    <property name="age" value="22"/>
</bean>

<bean id="person" class="MyPackage.Person">
    <property name="name" value="cyy"/>
    <property name="age">
        <bean id="student.age" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
    </property>
</bean>

在為person的age屬性注入值的時候,我們通過另一個bean的值為其注入,這個bean就是PropertyPathFactoryBean,其中我們通過它的id屬性指定需要調用Student對象的getAge方法作為返回值。

當然,我們也可以將PropertyPathFactoryBean返回的值定義成新的bean并指定它id屬性,保存在容器中。例如:

<bean id="student" class="MyPackage.Student">
    <property name="name" value="single"/>
    <property name="age" value="22"/>
</bean>

<bean id="stuAge" class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
    <property name="targetBeanName" value="student"/>
    <property name="propertyPath" value="age"/>
</bean>

<bean id="person" class="MyPackage.Person">
    <property name="name" value="cyy"/>
    <property name="age" ref="stuAge"/>
</bean>

stuAge中,我們通過注入PropertyPathFactoryBean的targetBeanName屬性值,告訴它目標對象在容器中的id,通過注入propertyPath屬性值,告訴它目標對象的具體getter方法的名稱。這樣,PropertyPathFactoryBean就可以調用具體的getter方法,將返回值注入到一個新bean中,此bean的id也已經被指定。于是我們在后續的bean配置中就可以直接使用該bean所包含的值了。

2、獲取靜態字段值
對于提供了getter方法的屬性,我們可以使用上述方法通過getter方法獲取到該屬性的值。對于并為提供getter方法的屬性值,我們也可以直接獲取,但前提是該屬性訪問權限足夠(private肯定是不能夠獲取得到的)。本小節學習的是獲取靜態的字段,對于非靜態字段,Spring也提供了方法獲取,但是一般的程序對于非靜態字段都會使用private修飾,提供良好的封裝性,因此我們也不能獲取得到,所以對于非靜態字段的獲取意義不大。

和前面一樣,想要獲取一個靜態字段的值需要以下兩個步驟:

  • 指定具體類名
  • 指定具體字段名

對于靜態字段的獲取,我們使用Spring中的 FiledRetrievingFactoryBean。和上述情況類似,可以直接賦值注入,也可以重新定義成bean保存在容器中。例如:

<bean id="person" class="MyPackage.Person">
    <property name="name" value="cyy"/>
    <property name="age">
        <bean id="MyPackage.BeanStaticClass.age" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>
    </property>
</bean>

其中,MyPackage.BeanStaticClass是一個類,其中有一個age的靜態字段,在這之前我們已經為該靜態字段賦值了,此處我們依然使用和PropertyPathFactoryBean類似的用法。它的第二種用法如下:

<bean id="staticAge" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
    <property name="targetClass" value="MyPackage.BeanStaticClass"/>
    <property name="targetField" value="age"/>
</bean>

<bean id="person" class="MyPackage.Person">
    <property name="name" value="cyy"/>
    <property name="age" ref="staticAge"/>
</bean>

用法類似,此處不再贅述。

3、獲取任意方法的返回值
根據方法的類型不同,我們大致可以分為以下兩個類別:

  • 靜態方法的調用
  • 實例方法的調用

不同類型的方法調用需要指定的參數類型也是不盡相同的。
對于靜態方法:

  • 指定調用類的名稱
  • 指定調用類的方法名稱
  • 指定需要傳入的參數

對于實例方法:

  • 指定調用實例的名稱
  • 指定調用實例中的方法名稱
  • 指定需要傳入的參數

例如:

<bean id="person" class="MyPackage.Person">
    <property name="name" value="cyy"/>
    <property name="age">
        <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
            <property name="targetClass" value="MyPackage.BeanStaticClass"/>
            <property name="targetMethod" value="getMyAge"/>
        </bean>
    </property>
</bean>

兩個屬性值,一個指定了目標類的名稱,一個指定了目標方法的名稱,如果需要傳入參數,可以使用Arguments屬性通過list傳入參數數組。實例方法的調用類似,此處不再贅述了。

至此,我們對于Spring中bean的配置做了進一步的理解,限于篇幅,有關XML Schema和SpEL部分內容留待下篇。

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

推薦閱讀更多精彩內容