Spring框架學(xué)習(xí)之高級(jí)依賴關(guān)系配置(一)

?????上篇文章我們對(duì)Spring做了初步的學(xué)習(xí),了解了基本的依賴注入思想、學(xué)會(huì)簡(jiǎn)單的配置bean、能夠使用Spring容器管理我們的bean實(shí)例等。但這還只是相對(duì)較淺顯的內(nèi)容,本篇將介紹bean的相關(guān)更高級(jí)的配置,主要涉及內(nèi)容如下:

  • 三種方式配置Bean
  • 深入理解容器中的Bean
  • 管理Bean的生命周期
  • 高級(jí)的依賴關(guān)系配置
  • 使用XML Schema簡(jiǎn)化DTD配置
  • 使用SpEL表達(dá)式語言

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

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

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

靜態(tài)工廠配置bean實(shí)例:
使用靜態(tài)工廠配置bean實(shí)例,在bean元素中需要指定至少兩個(gè)屬性值。

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

下面我們定義一個(gè)靜態(tài)工廠類及其靜態(tài)工廠方法:

/*person bean*/
public class Person {
    private String name;
    private int age;
    private String address;
    //省略setter方法
}
/*靜態(tài)工廠類及靜態(tài)工廠方法*/
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>

如果需要向工廠傳入?yún)?shù),可以使用元素<constructor-arg value="" />傳入?yún)?shù)。最終外部從容器中獲取person實(shí)例,打印信息:

這里寫圖片描述

這里需要再說明一點(diǎn)的是,除了使用<constructor-arg value="" />傳入?yún)?shù)去初始化bean的屬性外,我們也是可以通過property元素驅(qū)動(dòng)Spring再次執(zhí)行person的setter方法的,例如上述未被初始化的address屬性也可以在配置bean的時(shí)候進(jìn)行初始化。

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

實(shí)例工廠配置bean實(shí)例
實(shí)例工廠生成bean實(shí)例的配置其實(shí)和靜態(tài)工廠是類似的,只不過一個(gè)調(diào)用的是靜態(tài)方法,一個(gè)調(diào)用的是實(shí)例方法而已。使用實(shí)例工廠創(chuàng)建bean需要配置以下屬性:

  • factory-bean:指定工廠的實(shí)例
  • factory-method:指定工廠方法

這種方式和靜態(tài)工廠方法創(chuàng)建bean的方式及其類似,此處不再贅述。

顯然,后兩者于前者對(duì)于配置bean實(shí)例來說是兩種截然不同的方式,一種是聲明式配置,由Spring替我們生成bean實(shí)例,另一種則是我們程序員手動(dòng)的去返回bean實(shí)例,各有各的優(yōu)缺點(diǎn),適情況選擇。

二、深入理解容器中的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>

我們配置兩個(gè)bean實(shí)例,但是發(fā)現(xiàn)這兩個(gè)bean中存在大量相同的信息。如果容器中的bean越來越多,那么這樣大范圍的重復(fù)代碼必然導(dǎo)致整個(gè)配置文件臃腫,煩雜。

Spring中為我們提供一種機(jī)制,讓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>

我們抽象出來一個(gè)id為info的bean,該bean中初始化屬性name和age的值,然后我們的person和age bean通過屬性parent繼承了info,那么他們的相應(yīng)屬性的值將繼承自info。當(dāng)然,如果父bean和子bean中對(duì)同一屬性做了初始化,結(jié)果會(huì)用子bean中的值覆蓋父bean中的值注入到具體的bean實(shí)例中。

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

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

包括abstract屬性也是不能被繼承的。這里需要對(duì)比于Java中的類繼承機(jī)制,類的繼承關(guān)系其實(shí)是一種屬性字段和方法的繼承,而bean的繼承主要是屬性及其值的繼承。一個(gè)傾向于結(jié)構(gòu)上的繼承關(guān)系,一個(gè)則傾向于值上的繼承關(guān)系。

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

/*person類實(shí)現(xiàn)了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());

輸出結(jié)果:

person

當(dāng)容器創(chuàng)建person實(shí)例之后,它掃描該實(shí)例是否實(shí)現(xiàn)了接口BeanNameAware,如果實(shí)現(xiàn)了該接口,那么容器將自動(dòng)調(diào)用該實(shí)例中的setBeanName方法,并將當(dāng)前實(shí)例的id作為參數(shù)傳入,于是我們就可以保存下該實(shí)例在容器中的id。

三、Bean的生命周期
?????在Spring容器中,只有作用域?yàn)閟ingleton的bean才會(huì)被容器追蹤,而對(duì)于作用域?yàn)閜rototype的bean,容器只負(fù)責(zé)將它實(shí)例化出來,并不會(huì)追蹤它何時(shí)被初始化,何時(shí)被銷毀等。Spring容器提供兩個(gè)時(shí)機(jī)供我們追蹤Bean的生命周期:

  • 注入依賴結(jié)束時(shí)
  • Bean實(shí)例被銷毀時(shí)

對(duì)于第一種方式,我們只需要在定義bean的時(shí)候?yàn)槠渲付?init-method屬性的值即可。該屬性的值是一個(gè)方法的名稱,容器會(huì)在注入依賴結(jié)束的時(shí)候自動(dòng)調(diào)用實(shí)例中的該方法。例如:

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

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

配置bean:

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

這樣,當(dāng)容器對(duì)person完成注入依賴的時(shí)候,就會(huì)自動(dòng)調(diào)用我們?yōu)槠渲付ǖ膇nit方法。代碼比較簡(jiǎn)單,就不貼出運(yùn)行結(jié)果了。

對(duì)于第二個(gè)時(shí)機(jī),其實(shí)也是類似,只需要配置 屬性destory-method的值即可在bean被銷毀之前調(diào)用。此處不再贅述。

四、高級(jí)的依賴關(guān)系配置
?????一直以來,我們對(duì)于依賴關(guān)系的注入,要么使用常量注入到屬性中,要么使用引用注入到容器中。相對(duì)而言,這兩種方式對(duì)屬性的注入來說,幾乎是把"死值"注入給屬性,這樣的程序靈活性必然很差,我們平常也很少使用Spring為屬性注入固定的常量值。Spring中允許我們把任意方法的返回值、類或?qū)ο蟮膶傩灾狄约捌渌鸼ean的引用注入給我們的屬性。

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

  • targetObject:告訴容器需要調(diào)用那個(gè)bean實(shí)例
  • propertyPath:告訴容器需要調(diào)用那個(gè)屬性的getter方法

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

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

下面我們給出配置bean的代碼段,對(duì)于person中age我們調(diào)用Student實(shí)例中g(shù)etage作為注入值。

<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屬性注入值的時(shí)候,我們通過另一個(gè)bean的值為其注入,這個(gè)bean就是PropertyPathFactoryBean,其中我們通過它的id屬性指定需要調(diào)用Student對(duì)象的getAge方法作為返回值。

當(dāng)然,我們也可以將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屬性值,告訴它目標(biāo)對(duì)象在容器中的id,通過注入propertyPath屬性值,告訴它目標(biāo)對(duì)象的具體getter方法的名稱。這樣,PropertyPathFactoryBean就可以調(diào)用具體的getter方法,將返回值注入到一個(gè)新bean中,此bean的id也已經(jīng)被指定。于是我們?cè)诤罄m(xù)的bean配置中就可以直接使用該bean所包含的值了。

2、獲取靜態(tài)字段值
對(duì)于提供了getter方法的屬性,我們可以使用上述方法通過getter方法獲取到該屬性的值。對(duì)于并為提供getter方法的屬性值,我們也可以直接獲取,但前提是該屬性訪問權(quán)限足夠(private肯定是不能夠獲取得到的)。本小節(jié)學(xué)習(xí)的是獲取靜態(tài)的字段,對(duì)于非靜態(tài)字段,Spring也提供了方法獲取,但是一般的程序?qū)τ诜庆o態(tài)字段都會(huì)使用private修飾,提供良好的封裝性,因此我們也不能獲取得到,所以對(duì)于非靜態(tài)字段的獲取意義不大。

和前面一樣,想要獲取一個(gè)靜態(tài)字段的值需要以下兩個(gè)步驟:

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

對(duì)于靜態(tài)字段的獲取,我們使用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是一個(gè)類,其中有一個(gè)age的靜態(tài)字段,在這之前我們已經(jīng)為該靜態(tài)字段賦值了,此處我們依然使用和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、獲取任意方法的返回值
根據(jù)方法的類型不同,我們大致可以分為以下兩個(gè)類別:

  • 靜態(tài)方法的調(diào)用
  • 實(shí)例方法的調(diào)用

不同類型的方法調(diào)用需要指定的參數(shù)類型也是不盡相同的。
對(duì)于靜態(tài)方法:

  • 指定調(diào)用類的名稱
  • 指定調(diào)用類的方法名稱
  • 指定需要傳入的參數(shù)

對(duì)于實(shí)例方法:

  • 指定調(diào)用實(shí)例的名稱
  • 指定調(diào)用實(shí)例中的方法名稱
  • 指定需要傳入的參數(shù)

例如:

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

兩個(gè)屬性值,一個(gè)指定了目標(biāo)類的名稱,一個(gè)指定了目標(biāo)方法的名稱,如果需要傳入?yún)?shù),可以使用Arguments屬性通過list傳入?yún)?shù)數(shù)組。實(shí)例方法的調(diào)用類似,此處不再贅述了。

至此,我們對(duì)于Spring中bean的配置做了進(jìn)一步的理解,限于篇幅,有關(guān)XML Schema和SpEL部分內(nèi)容留待下篇。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容