Spring框架學(xué)習(xí)之依賴注入

?????Spring框架從2004年發(fā)布的第一個版本以來坏瘩,如今已經(jīng)迭代到5.x,逐漸成為JavaEE開發(fā)中必不可少的框架之一壮虫,也有人稱它為Java下的第一開源平臺奋岁。單從Spring的本身來說思瘟,它貫穿著整個表現(xiàn)層,業(yè)務(wù)層與持久層闻伶,它并沒有取代其他框架的意思滨攻,而更多的是從整體上管理這些框架,降低系統(tǒng)的耦合性蓝翰。系列文章將逐漸完成對Spring的學(xué)習(xí)铡买,本篇首先學(xué)習(xí)它的一個核心機制:依賴注入,主要涉及內(nèi)容如下:

  • 理解依賴注入
  • 理解Spring容器
  • 配置和使用bean

一霎箍、理解依賴注入
?????在正式介紹依賴注入之前奇钞,我們先通過一個簡單的Spring程序感受下Spring的一個最常用的功能。首先漂坏,導(dǎo)入必需的四個Spring包和一個它依賴的日志包:

  • spring-beans-4.0.0.RELEASE.jar
  • spring-context-4.0.0.RELEASE.jar
  • spring-core-4.0.0.RELEASE.jar
  • spring-expression-4.0.0.RELEASE.jar
  • commons-logging-1.2.jar

然后創(chuàng)建兩個類景埃,

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

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setParents(Parents parents) {
        this.parents = parents;
    }
}
public class Parents {
    private String pFatherName;
    private String pMotherName;

    public void setpFatherName(String pFatherName) {
        this.pFatherName = pFatherName;
    }

    public void setpMotherName(String pMotherName) {
        this.pMotherName = pMotherName;
    }
}

此時,我們的每個Person實例都在內(nèi)部引用了一個Parents實例顶别,那么當(dāng)我們想要獲取Person對象的時候谷徙,就需要首先初始化一個Parents實例給Person對象,程序如下:

public static void main(String[] args){
    Parents p = new Parents();
    p.setpFatherName("father");
    p.setpMotherName("mother");

    Person person = new Person();
    person.setName("single");
    person.setAge(22);
    person.setParents(p);
        
}

傳統(tǒng)的寫法驯绎,我們構(gòu)建一個person實例需要這么多行代碼完慧,而Spring給我們帶來的改變就是,通過構(gòu)建Spring容器剩失,所有的對象都會在容器中生成屈尼,外部只需要向容器索要對象即可。例如:

//新建Spring配置文件,命名為application.xml
//此處省略XML頭文件部分
<bean id="parents" class="MyPackage.Parents">
    <property name="pFatherName" value="father"/>
    <property name="pMotherName" value="mother"/>
</bean>

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

然后看我們的主程序如何獲取person實例:

public static void main(String[] args){
    ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
    Person person  = (Person) context.getBean("person");
}

一共兩行代碼拴孤,第一行代碼用于創(chuàng)建Spring容器脾歧,第二行代碼用于獲取容器中名為person的Person實例。在Spring配置文件加載的時候演熟,容器會初始化所有bean鞭执,也就是說所有配置在容器中的bean都會被創(chuàng)建實例并保存在容器中等待調(diào)用。依賴注入就是說芒粹,在容器加載結(jié)束之后兄纺,所有實例的屬性都被容器注入相應(yīng)的值,不需要程序員手動去賦值化漆。而在外部獲取該實例的時候不需要知道容器是如何為各個屬性注入值的估脆。如果某個bean關(guān)聯(lián)了其他bean,那么容器也會為它自動注入其他bean的引用获三。

依賴注入主要有兩種方式旁蔼,一種是設(shè)值注入锨苏,另一種是構(gòu)造注入,我們將在介紹bean的配置的時候詳細(xì)學(xué)習(xí)棺聊。

二伞租、理解Spring容器
?????Spring容器就相當(dāng)于一個大的工廠一樣,所有的bean都是工廠中的產(chǎn)品限佩。BeanFactory是Spring容器的最基本的接口葵诈,它負(fù)責(zé)配置、創(chuàng)建和管理Bean祟同,該接口中有如下幾個方法:

  • Object getBean(String var1):返回容器中id為var1的實例
  • <T>T getBean(String var1, Class<T> var2):返回容器中id為var1的實例作喘,并轉(zhuǎn)換該實例類型為var2
  • boolean containsBean(String var1):返回容器中是否包含id為var1 的實例
  • Class<?> getType(String var1):返回容器中id為var1的實例的所屬類型

ApplicationContext是BeanFactory的子接口,它擴展了BeanFactory晕城,提供更加完善的容器操作泞坦,對于大部分的JavaEE應(yīng)用來說,使用ApplicationContext作為Spring容器砖顷。它也有幾個常用的實現(xiàn)類:

  • FileSystemXmlApplicationContext:表示從文件系統(tǒng)中加載配置文件并創(chuàng)建Spring容器
  • ClassPathXmlApplicationContext:表示從應(yīng)用的類路徑(src)下加載配置文件并創(chuàng)建Spring容器
  • XmlWebApplicationContext和AnnotationConfigWebApplicationContex:它們主要應(yīng)用于web應(yīng)用中

一般來說贰锁,首選ApplicationContext作為Spring容器,除非在一些對內(nèi)存要求比較苛刻的應(yīng)用中才考慮使用BeanFactory滤蝠。

三豌熄、配置和管理Bean
?????在Spring的配置文件中,我們使用bean標(biāo)簽配置一個實例對象物咳。例如:

<bean id="person" class="MyPackage.Person">

</bean>

通常來說锣险,我們配置bean的時候會指定一個id屬性值和一個class屬性值,id屬性用于標(biāo)識該實例览闰,是該實例在容器中的唯一標(biāo)識芯肤,class屬性指向該實例的類型。顯然框架會利用反射根據(jù)這個class屬性值調(diào)用newInstance方法創(chuàng)建該類的一個實例對象焕济。

下面我們學(xué)習(xí)下bean的作用域纷妆,
容器中的bean支持以下幾種不同范圍的作用域:

  • singleton:單例模式,在整個容器中晴弃,該bean只會被創(chuàng)建一次。
  • prototype:該模式指定逊拍,每次外部調(diào)用getBean獲取該實例的時候上鞠,都會創(chuàng)建一個新的實例對象。
  • request:在同一次http請求中芯丧,程序請求該bean將會得到同一個實例芍阎。
  • session:在同一次http會話期間,程序?qū)υ揵ean的請求將會得到同一個實例缨恒。
  • global session:在全局的session會話期間對應(yīng)同一個實例谴咸。

Spring中默認(rèn)bean的作用域為singleton轮听,即每個bean只會在容器中被創(chuàng)建一次。例如:

/*配置一個Date實例*/
<bean id="date" class="java.util.Date" scope="singleton">
        
</bean>
/*獲取該實例*/
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
Date date = (Date) context.getBean("date");
Thread.sleep(1000);
Date date2 = (Date) context.getBean("date");

System.out.println("date:"+date);
System.out.println("date2:"+date2);

輸出結(jié)果:

date:Thu Nov 02 13:35:15 CST 2017
date2:Thu Nov 02 13:35:15 CST 2017

顯然岭佳,這兩次獲取的date是同一實例對象血巍,我們修改bean的配置:

<bean id="date" class="java.util.Date" scope="prototype">

</bean>

再次執(zhí)行程序:

date:Thu Nov 02 13:39:09 CST 2017
date2:Thu Nov 02 13:39:10 CST 2017

顯然,兩個date的值并不相等珊随,所以它們并不指向同一實例述寡。當(dāng)然,Spring中默認(rèn)bean的作用域為singleton叶洞。

下面我們學(xué)習(xí)如何配置依賴關(guān)系鲫凶,讓容器為我們注入依賴,
根據(jù)注入方式的不同衩辟,Bean的依賴注入可以有如下兩種形式:

  • 設(shè)值注入:通過<property..>元素驅(qū)動Spring執(zhí)行setter方法實現(xiàn)螟炫。
  • 構(gòu)造注入:通過<constructor-arg...>元素驅(qū)動Spring執(zhí)行有參構(gòu)造器完成注入。

我們首先看看設(shè)值注入的使用艺晴,

/*定義一個person類*/
public class Person {
    private String name;
    private int age;
    
    public void setName(String name) {
        System.out.println("調(diào)用setname方法注入name的值");
        this.name = name;
    }

    public void setAge(int age) {
        System.out.println("調(diào)用setage方法注入age的值");
        this.age = age;
    }
}
/*配置一個person類實例*/
<bean id="person" class="MyPackage.Person">
    <property name="name" value="single"/>
    <property name="age" value="22"/>
</bean>
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
Person person = (Person) context.getBean("person");
System.out.println(person);

從容器中獲取person實例昼钻,輸出結(jié)果:

這里寫圖片描述

可以看到,當(dāng)容器初始化完畢的時候财饥,會通過反射創(chuàng)建每個bean元素所對應(yīng)的實例换吧,遇到property元素,Spring則會反射調(diào)用該實例的setter方法初始化這些屬性值钥星,也可以叫做注入依賴沾瓦。這種方式的注入依賴,唯一需要的是類所對應(yīng)的屬性必須配置一個setter方法谦炒。

構(gòu)造注入方式:

/*向person中增加一個有參構(gòu)造器*/
public Person(String name,int age){
    System.out.println("調(diào)用構(gòu)造器注入屬性值");
    this.name = name;
    this.age = age;
}

配置bean:

<bean id="person" class="MyPackage.Person">
    <constructor-arg name="name" value="single"/>
    <constructor-arg name="age" value="22"/>
</bean>

輸出結(jié)果:

這里寫圖片描述

很顯然贯莺,Spring在加載配置文件的時候,發(fā)現(xiàn)bean的配置中并沒有<costructor-arg>元素宁改,那么它將會驅(qū)動默認(rèn)的構(gòu)造器創(chuàng)建一個類實例缕探,否則將<costructor-arg>元素的個數(shù)驅(qū)動具有相對應(yīng)的構(gòu)造器。

總的來說还蹲,兩者都有優(yōu)缺點爹耗,設(shè)置注入更容易理解和使用,構(gòu)造注入只允許屬性的值在構(gòu)造實例的時候賦值谜喊,一旦實例構(gòu)建完成潭兽,其屬性將不具備修改的能力,使得依賴關(guān)系不易被破壞斗遏,但是大量重復(fù)臃腫的構(gòu)造代碼使得程序很笨重山卦。一般建議以設(shè)值依賴為主,構(gòu)造注入為輔诵次。

Spring允許通過以下幾種類型的的元素作為setter方法的參數(shù)傳入類屬性的setter方法中账蓉,

  • 普通屬性值
  • 引用
  • 內(nèi)部bean
  • 集合以及屬性集

?????1枚碗、普通屬性值
?????對于基本類型已經(jīng)String類型的屬性值,我們通過

<property name="" value="" />

直接將值填在value中即可铸本,Spring調(diào)用XML的解析器將所有的String自動轉(zhuǎn)換為對應(yīng)的參數(shù)類型并傳入setter方法中肮雨。

?????2、引用類型
?????我們定義一個Person類和一個Parents類:

public class Parents {
    private String pFatherName;
    private String pMotherName;
    //省略setter方法
}

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

我們的Person實例內(nèi)部有一個Parents 類型的屬性归敬,那么容器在注入的時候該如何將一個Parents 類型的實例注入到Person的parents屬性中呢酷含?

<bean id="parents" class="MyPackage.Parents">
    <property name="pFatherName" value="father"/>
    <property name="pMotherName" value="mother"/>
</bean>

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

我們通過<property>元素的ref屬性配置這種引用依賴,首先容器會創(chuàng)建一個名為parents的Parents實例并為其內(nèi)部的各個屬性注入數(shù)值汪茧,接著容器會創(chuàng)建一個名為person的Person實例椅亚,并注入其普通屬性值。當(dāng)為其parents屬性注入值的時候舱污,傳入的是已存在的實例parents的引用呀舔。

?????3、定義內(nèi)部bean
?????如果某個bean不想被容器外部引用扩灯,而只想作為一個屬性的值傳入setter方法中媚赖,那么我們可以將它定義成內(nèi)部bean。例如:

<bean id="person" class="MyPackage.Person">
    <property name="name" value="single"/>
    <property name="age" value="22"/>
    <property name="parents">
        <bean class="MyPackage.Parents">
            <property name="pFatherName" value="father"/>
            <property name="pMotherName" value="mother"/>
        </bean>
    </property>
</bean>

這樣我們就配置了一個內(nèi)部bean珠插,該bean沒有標(biāo)識惧磺,用完就丟失對它的引用了。其實定義內(nèi)部bean來給某個實例屬性賦值和使用ref引用在本質(zhì)上是一樣的捻撑。

?????4磨隘、集合屬性以及屬性集
?????我們重新定義Person類如下:

public class Person {
    private List<String> address;
    private Map<String,String> creditCard;
    private Set<String> hobbies;
    //省略setter方法
}

各種類型的集合的配置如下:

<bean id="person" class="MyPackage.Person">
    <property name="address">
        <list>
            <!--每個value,ref顾患,bean都對應(yīng)于list中的一個元素-->
            <value>南京</value>
            <value>上海</value>
            <value>北京</value>
        </list>
    </property>
    <property name="creditCard">
        <map>
            <!--每個entry元素對應(yīng)于一個鍵值對番捂,map中的-->
            <entry key="中國銀行" value="3231243234"></entry>
            <entry key="江蘇銀行" value="5453454434"></entry>
        </map>
    </property>
    <property name="hobbies">
        <set>
            <!--每個value,ref江解,bean都對應(yīng)于list中的一個元素-->
            <value>乒乓球</value>
            <value>網(wǎng)球</value>
        </set>
    </property>
</bean>

屬性集的配置和map的配置類似设预,使用鍵值對的形式進(jìn)行配置:

<property name="properties">
    <props>
        <prop key="hello">single</prop>
    </props>
</property>

至此,我們簡單的介紹了Spring的依賴注入的一些基本的內(nèi)容犁河,有關(guān)bean的更高級的使用將在后續(xù)文章中進(jìn)行介紹鳖枕。總結(jié)不到之處桨螺,望指出!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末彭谁,一起剝皮案震驚了整個濱河市允扇,隨后出現(xiàn)的幾起案子则奥,更是在濱河造成了極大的恐慌狭园,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件唱矛,死亡現(xiàn)場離奇詭異,居然都是意外死亡绎谦,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進(jìn)店門窃肠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人冤留,你說我怎么就攤上這事∠伺” “怎么了糯而?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長泊窘。 經(jīng)常有香客問我熄驼,道長,這世上最難降的妖魔是什么州既? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任谜洽,我火速辦了婚禮,結(jié)果婚禮上吴叶,老公的妹妹穿的比我還像新娘阐虚。我一直安慰自己,他們只是感情好蚌卤,可當(dāng)我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布实束。 她就那樣靜靜地躺著,像睡著了一般逊彭。 火紅的嫁衣襯著肌膚如雪咸灿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天侮叮,我揣著相機與錄音避矢,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛审胸,可吹牛的內(nèi)容都是我干的亥宿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼砂沛,長吁一口氣:“原來是場噩夢啊……” “哼烫扼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起碍庵,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤映企,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后静浴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體堰氓,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年马绝,在試婚紗的時候發(fā)現(xiàn)自己被綠了豆赏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡富稻,死狀恐怖掷邦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情椭赋,我是刑警寧澤抚岗,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布哪怔,位于F島的核電站,受9級特大地震影響胚委,放射性物質(zhì)發(fā)生泄漏叉信。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一硅急、第九天 我趴在偏房一處隱蔽的房頂上張望营袜。 院中可真熱鬧丑罪,春花似錦、人聲如沸啸驯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽厌衙。三九已至,卻和暖如春榕暇,著一層夾襖步出監(jiān)牢的瞬間喻杈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工缴啡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留业栅,地道東北人碘裕。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓帮孔,卻偏偏與公主長得像夕玩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子禽作,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內(nèi)容