Spring(3)——裝配 Spring Bean 詳解

裝配 Bean 的概述

前面已經(jīng)介紹了 Spring IoC 的理念和設(shè)計(jì)难菌,這一篇文章將介紹的是如何將自己開發(fā)的 Bean 裝配到 Spring IoC 容器中搀捷。

大部分場(chǎng)景下溺蕉,我們都會(huì)使用 ApplicationContext 的具體實(shí)現(xiàn)類,因?yàn)閷?duì)應(yīng)的 Spring IoC 容器功能相對(duì)強(qiáng)大码撰。

而在 Spring 中提供了 3 種方法進(jìn)行配置:

  • 在 XML 文件中顯式配置
  • 在 Java 的接口和類中實(shí)現(xiàn)配置
  • 隱式 Bean 的發(fā)現(xiàn)機(jī)制和自動(dòng)裝配原則

方式選擇的原則

在現(xiàn)實(shí)的工作中,這 3 種方式都會(huì)被用到堵第,并且在學(xué)習(xí)和工作之中常常混合使用隧出,所以這里給出一些關(guān)于這 3 種優(yōu)先級(jí)的建議:

1.最優(yōu)先:通過隱式 Bean 的發(fā)現(xiàn)機(jī)制和自動(dòng)裝配的原則踏志。
基于約定由于配置的原則,這種方式應(yīng)該是最優(yōu)先的

  • 好處: 減少程序開發(fā)者的決定權(quán)胀瞪,簡(jiǎn)單又不失靈活针余。

2.其次:Java 接口和類中配置實(shí)現(xiàn)配置
在沒有辦法使用自動(dòng)裝配原則的情況下應(yīng)該優(yōu)先考慮此類方法

  • 好處: 避免 XML 配置的泛濫,也更為容易凄诞。
  • 典型場(chǎng)景: 一個(gè)父類有多個(gè)子類圆雁,比如學(xué)生類有兩個(gè)子類,一個(gè)男學(xué)生類和女學(xué)生類帆谍,通過 IoC 容器初始化一個(gè)學(xué)生類伪朽,容器將無法知道使用哪個(gè)子類去初始化,這個(gè)時(shí)候可以使用 Java 的注解配置去指定汛蝙。

3.最后:XML 方式配置
在上述方法都無法使用的情況下烈涮,那么也只能選擇 XML 配置的方式。

  • 好處: 簡(jiǎn)單易懂(當(dāng)然窖剑,特別是對(duì)于初學(xué)者)
  • 典型場(chǎng)景: 當(dāng)使用第三方類的時(shí)候坚洽,有些類并不是我們開發(fā)的,我們無法修改里面的代碼西土,這個(gè)時(shí)候就通過 XML 的方式配置使用了讶舰。

通過 XML 配置裝配 Bean

使用 XML 裝配 Bean 需要定義對(duì)應(yīng)的 XML,這里需要引入對(duì)應(yīng)的 XML 模式(XSD)文件需了,這些文件會(huì)定義配置 Spring Bean 的一些元素跳昼,當(dāng)我們?cè)?IDEA 中創(chuàng)建 XML 文件時(shí),會(huì)有友好的提示:

一個(gè)簡(jiǎn)單的 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">

</beans>

這就只是一個(gè)格式文件援所,引入了一個(gè) beans 的定義庐舟,引入了 xsd 文件,它是一個(gè)根元素住拭,這樣它所定義的元素將可以定義對(duì)應(yīng)的 Spring Bean

裝配簡(jiǎn)易值

先來一個(gè)最簡(jiǎn)單的裝配:

<bean id="c" class="pojo.Category">
    <property name="name" value="測(cè)試" />
</bean>

簡(jiǎn)單解釋一下:

  • id 屬性是 Spring 能找到當(dāng)前 Bean 的一個(gè)依賴的編號(hào)挪略,遵守 XML 語法的 ID 唯一性約束。必須以字母開頭滔岳,可以使用字母杠娱、數(shù)字、連字符谱煤、下劃線摊求、句號(hào)、冒號(hào)刘离,不能以 / 開頭室叉。
    不過 id 屬性不是一個(gè)必需的屬性睹栖,name 屬性也可以定義 bean 元素的名稱,能以逗號(hào)或空格隔開起多個(gè)別名茧痕,并且可以使用很多的特殊字符野来,比如在 Spring 和 Spring MVC 的整合中,就得使用 name 屬性來定義 bean 的名稱踪旷,并且使用 / 開頭曼氛。
    注意: 從 Spring 3.1 開始,id 屬性也可以是 String 類型了令野,也就是說 id 屬性也可以使用 / 開頭舀患,而 bean 元素的 id 的唯一性由容器負(fù)責(zé)檢查。
    如果 idname 屬性都沒有聲明的話气破,那么 Spring 將會(huì)采用 “全限定名#{number}” 的格式生成編號(hào)聊浅。 例如這里,如果沒有聲明 “id="c"” 的話堵幽,那么 Spring 為其生成的編號(hào)就是 “pojo.Category#0”狗超,當(dāng)它第二次聲明沒有 id 屬性的 Bean 時(shí)弹澎,編號(hào)就是 “pojo.Category#1”朴下,以此類推。
  • class 屬性顯然就是一個(gè)類的全限定名
  • property 元素是定義類的屬性苦蒿,其中的 name 屬性定義的是屬性的名稱殴胧,而 value 是它的值。

這樣的定義很簡(jiǎn)單佩迟,但是有時(shí)候需要注入一些自定義的類团滥,比如之前飲品店的例子,JuickMaker 需要用戶提供原料信息才能完成 juice 的制作:

<!-- 配置 srouce 原料 -->
<bean name="source" class="pojo.Source">
    <property name="fruit" value="橙子"/>
    <property name="sugar" value="多糖"/>
    <property name="size" value="超大杯"/>
</bean>

<bean name="juickMaker" class="pojo.JuiceMaker">
    <!-- 注入上面配置的id為srouce的Srouce對(duì)象 -->
    <property name="source" ref="source"/>
</bean>

這里先定義了一個(gè) name 為 source 的 Bean报强,然后再制造器中通過 ref 屬性去引用對(duì)應(yīng)的 Bean灸姊,而 source 正是之前定義的 Bean 的 name ,這樣就可以相互引用了秉溉。

  • 注入對(duì)象:使用 ref 屬性

裝配集合

有些時(shí)候我們需要裝配一些復(fù)雜的東西力惯,比如 Set、Map召嘶、List父晶、Array 和 Properties 等,為此我們?cè)?Packge【pojo】下新建一個(gè) ComplexAssembly 類:

package pojo;

import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class ComplexAssembly {
    
    private Long id;
    private List<String> list;
    private Map<String, String> map;
    private Properties properties;
    private Set<String> set;
    private String[] array;

    /* setter and getter */
}

這個(gè) Bean 沒有任何的實(shí)際意義弄跌,知識(shí)為了介紹如何裝配這些常用的集合類:

<bean id="complexAssembly" class="pojo.ComplexAssembly">
    <!-- 裝配Long類型的id -->
    <property name="id" value="1"/>
    
    <!-- 裝配List類型的list -->
    <property name="list">
        <list>
            <value>value-list-1</value>
            <value>value-list-2</value>
            <value>value-list-3</value>
        </list>
    </property>
    
    <!-- 裝配Map類型的map -->
    <property name="map">
        <map>
            <entry key="key1" value="value-key-1"/>
            <entry key="key2" value="value-key-2"/>
            <entry key="key3" value="value-key-2"/>
        </map>
    </property>
    
    <!-- 裝配Properties類型的properties -->
    <property name="properties">
        <props>
            <prop key="prop1">value-prop-1</prop>
            <prop key="prop2">value-prop-2</prop>
            <prop key="prop3">value-prop-3</prop>
        </props>
    </property>
    
    <!-- 裝配Set類型的set -->
    <property name="set">
        <set>
            <value>value-set-1</value>
            <value>value-set-2</value>
            <value>value-set-3</value>
        </set>
    </property>
    
    <!-- 裝配String[]類型的array -->
    <property name="array">
        <array>
            <value>value-array-1</value>
            <value>value-array-2</value>
            <value>value-array-3</value>
        </array>
    </property>
</bean>
  • 總結(jié):
  • List 屬性為對(duì)應(yīng)的 <list> 元素進(jìn)行裝配甲喝,然后通過多個(gè) <value> 元素設(shè)值
  • Map 屬性為對(duì)應(yīng)的 <map> 元素進(jìn)行裝配,然后通過多個(gè) <entry> 元素設(shè)值铛只,只是 entry 包含一個(gè)鍵值對(duì)(key-value)的設(shè)置
  • Properties 屬性為對(duì)應(yīng)的 <properties> 元素進(jìn)行裝配埠胖,通過多個(gè) <property> 元素設(shè)值糠溜,只是 properties 元素有一個(gè)必填屬性 key ,然后可以設(shè)置值
  • Set 屬性為對(duì)應(yīng)的 <set> 元素進(jìn)行裝配直撤,然后通過多個(gè) <value> 元素設(shè)值
  • 對(duì)于數(shù)組而言诵冒,可以使用 <array> 設(shè)置值,然后通過多個(gè) <value> 元素設(shè)值谊惭。

上面看到了對(duì)簡(jiǎn)單 String 類型的各個(gè)集合的裝載汽馋,但是有些時(shí)候可能需要更為復(fù)雜的裝載,比如一個(gè) List 可以是一個(gè)系列類的對(duì)象圈盔,為此需要定義注入的相關(guān)信息豹芯,其實(shí)跟上面的配置沒什么兩樣,只不過加入了 ref 這一個(gè)屬性而已:

  • 集合注入總結(jié):
  • List 屬性使用 <list> 元素定義注入驱敲,使用多個(gè) <ref> 元素的 Bean 屬性去引用之前定義好的 Bean
<property name="list">
    <list>
        <ref bean="bean1"/>
        <ref bean="bean2"/>
    </list>
</property>
  • Map 屬性使用 <map> 元素定義注入铁蹈,使用多個(gè) <entry> 元素的 key-ref 屬性去引用之前定義好的 Bean 作為鍵,而用 value-ref 屬性引用之前定義好的 Bean 作為值
<property name="map">
    <map>
        <entry key-ref="keyBean" value-ref="valueBean"/>
    </map>
</property>
  • Set 屬性使用 <set> 元素定義注入众眨,使用多個(gè) <ref> 元素的 bean 去引用之前定義好的 Bean
<property name="set">
    <set>
        <ref bean="bean"/>
    </set>
</property>

命名空間裝配

除了上述的配置之外握牧, Spring 還提供了對(duì)應(yīng)的命名空間的定義,只是在使用命名空間的時(shí)候要先引入對(duì)應(yīng)的命名空間和 XML 模式(XSD)文件娩梨。

——【① c-命名空間】——

c-命名空間是在 Spring 3.0 中引入的沿腰,它是在 XML 中更為簡(jiǎn)潔地描述構(gòu)造器參數(shù)的方式,要使用它的話狈定,必須要在 XML 的頂部聲明其模式:

  • 注意:是通過構(gòu)造器參數(shù)的方式

現(xiàn)在假設(shè)我們現(xiàn)在有這么一個(gè)類:

package pojo;

public class Student {

    int id;
    String name;

    public Student(int id, String name) {
        this.id = id;
        this.name = name;
    }
    // setter and getter
}

在 c-命名空間和模式聲明之后颂龙,我們就可以使用它來聲明構(gòu)造器參數(shù)了:

<!-- 引入 c-命名空間之前 -->
<bean name="student1" class="pojo.Student">
    <constructor-arg name="id" value="1" />
    <constructor-arg name="name" value="學(xué)生1"/>
</bean>

<!-- 引入 c-命名空間之后 -->
<bean name="student2" class="pojo.Student"
      c:id="2" c:name="學(xué)生2"/>

c-命名空間屬性名以 “c:” 開頭,也就是命名空間的前綴纽什。接下來就是要裝配的構(gòu)造器參數(shù)名措嵌,在此之后如果需要注入對(duì)象的話則要跟上 -ref(如c:card-ref="idCard1",則對(duì) card 這個(gè)構(gòu)造器參數(shù)注入之前配置的名為 idCard1 的 bean)

很顯然芦缰,使用 c-命名空間屬性要比使用 <constructor-arg> 元素精簡(jiǎn)企巢,并且會(huì)直接引用構(gòu)造器之中參數(shù)的名稱,這有利于我們使用的安全性让蕾。

我們有另外一種替代方式:

<bean name="student2" class="pojo.Student"
      c:_0="3" c:_1="學(xué)生3"/>

我們將參數(shù)的名稱替換成了 “0” 和 “1” 浪规,也就是參數(shù)的索引。因?yàn)樵?XML 中不允許數(shù)字作為屬性的第一個(gè)字符涕俗,因此必須要添加一個(gè)下劃線來作為前綴罗丰。

——【② p-命名空間】——

c-命名空間通過構(gòu)造器注入的方式來配置 bean,p-命名空間則是用setter的注入方式來配置 bean 再姑,同樣的萌抵,我們需要引入聲明:

然后我們就可以通過 p-命名空間來設(shè)置屬性:

<!-- 引入p-命名空間之前 -->
<bean name="student1" class="pojo.Student">
    <property name="id" value="1" />
    <property name="name" value="學(xué)生1"/>
</bean>

<!-- 引入p-命名空間之后 -->
<bean name="student2" class="pojo.Student" 
      p:id="2" p:name="學(xué)生2"/>

我們需要先刪掉 Student 類中的構(gòu)造函數(shù),不然 XML 約束會(huì)提示我們配置 <constructor-arg> 元素。

同樣的绍填,如果屬性需要注入其他 Bean 的話也可以在后面跟上 -ref

    <bean name="student2" class="pojo.Student"
          p:id="2" p:name="學(xué)生2" p:cdCard-ref="cdCard1"/>
——【③ util-命名空間】——

工具類的命名空間霎桅,可以簡(jiǎn)化集合類元素的配置,同樣的我們需要引入其聲明(無需擔(dān)心怎么聲明的問題讨永,IDEA會(huì)有很友好的提示):

我們來看看引入前后的變化:

<!-- 引入util-命名空間之前 -->
<property name="list">
    <list>
        <ref bean="bean1"/>
        <ref bean="bean2"/>
    </list>
</property>

<!-- 引入util-命名空間之后 -->
<util:list id="list">
    <ref bean="bean1"/>
    <ref bean="bean2"/>
</util:list>

<util:list> 只是 util-命名空間中的多個(gè)元素之一滔驶,下表提供了 util-命名空間提供的所有元素:

元素 描述
<util:constant> 引用某個(gè)類型的 public static 域,并將其暴露為 bean
<util:list> 創(chuàng)建一個(gè) java.util.List 類型的 bean卿闹,其中包含值或引用
<util:map> 創(chuàng)建一個(gè) java.util.map 類型的 bean揭糕,其中包含值或引用
<util:properties> 創(chuàng)建一個(gè) java.util.Properties 類型的 bean
<util:property-path> 引用一個(gè) bean 的屬性(或內(nèi)嵌屬性),并將其暴露為 bean
<util:set> 創(chuàng)建一個(gè) java.util.Set 類型的 bean锻霎,其中包含值或引用

引入其他配置文件

在實(shí)際開發(fā)中著角,隨著應(yīng)用程序規(guī)模的增加,系統(tǒng)中 <bean> 元素配置的數(shù)量也會(huì)大大增加旋恼,導(dǎo)致 applicationContext.xml 配置文件變得非常臃腫難以維護(hù)吏口。

  • 解決方案:讓 applicationContext.xml 文件包含其他配置文件即可
    使用 <import> 元素引入其他配置文件

1.在【src】文件下新建一個(gè) bean.xml 文件,寫好基礎(chǔ)的約束冰更,把 applicationContext.xml 文件中配置的 <bean> 元素復(fù)制進(jìn)去

2.在 applicationContext.xml 文件中寫入:

<import resource="bean.xml" />

3.運(yùn)行測(cè)試代碼产徊,仍然能正確獲取到 bean:


通過注解裝配 Bean

上面,我們已經(jīng)了解了如何使用 XML 的方式去裝配 Bean蜀细,但是更多的時(shí)候已經(jīng)不再推薦使用 XML 的方式去裝配 Bean舟铜,更多的時(shí)候回考慮使用注解(annotation) 的方式去裝配 Bean。

  • 優(yōu)勢(shì):
    1.可以減少 XML 的配置审葬,當(dāng)配置項(xiàng)多的時(shí)候深滚,臃腫難以維護(hù)
    2.功能更加強(qiáng)大奕谭,既能實(shí)現(xiàn) XML 的功能涣觉,也提供了自動(dòng)裝配的功能,采用了自動(dòng)裝配后血柳,程序猿所需要做的決斷就少了官册,更加有利于對(duì)程序的開發(fā),這就是“約定由于配置”的開發(fā)原則

在 Spring 中难捌,它提供了兩種方式來讓 Spring IoC 容器發(fā)現(xiàn) bean:

  • 組件掃描:通過定義資源的方式膝宁,讓 Spring IoC 容器掃描對(duì)應(yīng)的包,從而把 bean 裝配進(jìn)來根吁。
  • 自動(dòng)裝配:通過注解定義员淫,使得一些依賴關(guān)系可以通過注解完成。

使用@Compoent 裝配 Bean

我們把之前創(chuàng)建的 Student 類改一下:

package pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component(value = "student1")
public class Student {

    @Value("1")
    int id;
    @Value("student_name_1")
    String name;

    // getter and setter
}

解釋一下:

  • @Component注解:
    表示 Spring IoC 會(huì)把這個(gè)類掃描成一個(gè) bean 實(shí)例击敌,而其中的 value 屬性代表這個(gè)類在 Spring 中的 id介返,這就相當(dāng)于在 XML 中定義的 Bean 的 id:<bean id="student1" class="pojo.Student" />,也可以簡(jiǎn)寫成 @Component("student1"),甚至直接寫成 @Component 圣蝎,對(duì)于不寫的刃宵,Spring IoC 容器就默認(rèn)以類名來命名作為 id,只不過首字母小寫徘公,配置到容器中牲证。
  • @Value注解:
    表示值的注入,跟在 XML 中寫 value 屬性是一樣的关面。

這樣我們就聲明好了我們要?jiǎng)?chuàng)建的一個(gè) Bean坦袍,就像在 XML 中寫下了這樣一句話:

<bean name="student1" class="pojo.Student">
    <property name="id" value="1" />
    <property name="name" value="student_name_1"/>
</bean>

但是現(xiàn)在我們聲明了這個(gè)類,并不能進(jìn)行任何的測(cè)試等太,因?yàn)?Spring IoC 并不知道這個(gè) Bean 的存在键闺,這個(gè)時(shí)候我們可以使用一個(gè) StudentConfig 類去告訴 Spring IoC :

package pojo;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class StudentConfig {
}

這個(gè)類十分簡(jiǎn)單,沒有任何邏輯澈驼,但是需要說明兩點(diǎn):

  • 該類和 Student 類位于同一包名下
  • @ComponentScan注解:
    代表進(jìn)行掃描辛燥,默認(rèn)是掃描當(dāng)前包的路徑,掃描所有帶有 @Component 注解的 POJO缝其。

這樣一來挎塌,我們就可以通過 Spring 定義好的 Spring IoC 容器的實(shí)現(xiàn)類——AnnotationConfigApplicationContext 去生成 IoC 容器了:

ApplicationContext context = new AnnotationConfigApplicationContext(StudentConfig.class);
Student student = (Student) context.getBean("student1", Student.class);
student.printInformation();

這里可以看到使用了 AnnotationConfigApplicationContext 類去初始化 Spring IoC 容器,它的配置項(xiàng)是 StudentConfig 類内边,這樣 Spring IoC 就會(huì)根據(jù)注解的配置去解析對(duì)應(yīng)的資源榴都,來生成 IoC 容器了。

  • 明顯的弊端:
  • 對(duì)于 @ComponentScan 注解漠其,它只是掃描所在包的 Java 類嘴高,但是更多的時(shí)候我們希望的是可以掃描我們指定的類
  • 上面的例子只是注入了一些簡(jiǎn)單的值,測(cè)試發(fā)現(xiàn)和屎,通過 @Value 注解并不能注入對(duì)象

@Component 注解存在著兩個(gè)配置項(xiàng):

  • basePackages:它是由 base 和 package 兩個(gè)單詞組成的拴驮,而 package 還是用了復(fù)數(shù),意味著它可以配置一個(gè) Java 包的數(shù)組柴信,Spring 會(huì)根據(jù)它的配置掃描對(duì)應(yīng)的包和子包套啤,將配置好的 Bean 裝配進(jìn)來
  • basePackageClasses:它由 base、package 和 class 三個(gè)單詞組成随常,采用復(fù)數(shù)潜沦,意味著它可以配置多個(gè)類, Spring 會(huì)根據(jù)配置的類所在的包绪氛,為包和子包進(jìn)行掃描裝配對(duì)應(yīng)配置的 Bean

我們來試著重構(gòu)之前寫的 StudentConfig 類來驗(yàn)證上面兩個(gè)配置項(xiàng):

package pojo;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan(basePackages = "pojo")
public class StudentConfig {
}

//  —————————————————— 【 宇宙超級(jí)無敵分割線】—————————————————— 
package pojo;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan(basePackageClasses = pojo.Student.class)
public class StudentConfig {
}

驗(yàn)證都能通過唆鸡,bingo!

  • 對(duì)于 【basePackages】 和 【basePackageClasses】 的選擇問題:
    【basePackages】 的可讀性會(huì)更好一些枣察,所以在項(xiàng)目中會(huì)優(yōu)先選擇使用它争占,但是在需要大量重構(gòu)的工程中袄琳,盡量不要使用【basePackages】,因?yàn)楹芏鄷r(shí)候重構(gòu)修改包名需要反復(fù)地配置燃乍,而 IDE 不會(huì)給你任何的提示唆樊,而采用【basePackageClasses】會(huì)有錯(cuò)誤提示。

自動(dòng)裝配——@Autowired

上面提到的兩個(gè)弊端之一就是沒有辦法注入對(duì)象刻蟹,通過自動(dòng)裝配我們將解決這個(gè)問題逗旁。

所謂自動(dòng)裝配技術(shù)是一種由 Spring 自己發(fā)現(xiàn)對(duì)應(yīng)的 Bean,自動(dòng)完成裝配工作的方式舆瘪,它會(huì)應(yīng)用到一個(gè)十分常用的注解 @Autowired 上片效,這個(gè)時(shí)候 Spring 會(huì)根據(jù)類型去尋找定義的 Bean 然后將其注入,聽起來很神奇英古,讓我們實(shí)際來看一看:

1.先在 Package【service】下創(chuàng)建一個(gè) StudentService 接口:

package service;

public interface StudentService {
    public void printStudentInfo();
}

使用接口是 Spring 推薦的方式淀衣,這樣可以更為靈活,可以將定義和實(shí)現(xiàn)分離

2.為上面的接口創(chuàng)建一個(gè) StudentServiceImp 實(shí)現(xiàn)類:

package service;

import org.springframework.beans.factory.annotation.Autowired;
import pojo.Student;

@Component("studentService")
public class StudentServiceImp implements StudentService {

    @Autowired
    private Student student = null;

     // getter and setter

    public void printStudentInfo() {
        System.out.println("學(xué)生的 id 為:" + student.getName());
        System.out.println("學(xué)生的 name 為:" + student.getName());
    }
}

該實(shí)現(xiàn)類實(shí)現(xiàn)了接口的 printStudentInfo() 方法召调,打印出成員對(duì)象 student 的相關(guān)信息膨桥,這里的 @Autowired 注解,表示在 Spring IoC 定位所有的 Bean 后唠叛,這個(gè)字段需要按類型注入只嚣,這樣 IoC 容器就會(huì)尋找資源,然后將其注入艺沼。

3.編寫測(cè)試類:

// 第一步:修改 StudentConfig 類册舞,告訴 Spring IoC 在哪里去掃描它:
package pojo;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan(basePackages = {"pojo", "service"})
public class StudentConfig {
}

// 或者也可以在 XML 文件中聲明去哪里做掃描
<context:component-scan base-package="pojo" />
<context:component-scan base-package="service" />

// 第二步:編寫測(cè)試類:
package test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import pojo.StudentConfig;
import service.StudentService;
import service.StudentServiceImp;

public class TestSpring {

    public static void main(String[] args) {
        // 通過注解的方式初始化 Spring IoC 容器
        ApplicationContext context = new AnnotationConfigApplicationContext(StudentConfig.class);
        StudentService studentService = context.getBean("studentService", StudentServiceImp.class);
        studentService.printStudentInfo();
    }
}

運(yùn)行代碼:

  • 再次理解: @Autowired 注解表示在 Spring IoC 定位所有的 Bean 后,再根據(jù)類型尋找資源障般,然后將其注入调鲸。
  • 過程: 定義 Bean ——》 初始化 Bean(掃描) ——》 根據(jù)屬性需要從 Spring IoC 容器中搜尋滿足要求的 Bean ——》 滿足要求則注入
  • 問題: IoC 容器可能會(huì)尋找失敗,此時(shí)會(huì)拋出異常(默認(rèn)情況下挽荡,Spring IoC 容器會(huì)認(rèn)為一定要找到對(duì)應(yīng)的 Bean 來注入到這個(gè)字段藐石,但有些時(shí)候并不是一定需要,比如日志)
  • 解決: 通過配置項(xiàng) required 來改變徐伐,比如 @Autowired(required = false)

@Autowired 注解不僅僅能配置在屬性之上贯钩,還允許方法配置,常見的 Bean 的 setter 方法也可以使用它來完成注入办素,總之一切需要 Spring IoC 去尋找 Bean 資源的地方都可以用到,例如:

/* 包名和import */
public class JuiceMaker {
    ......
    @Autowired
    public void setSource(Source source) {
        this.source = source;
    }
}

在大部分的配置中都推薦使用這樣的自動(dòng)注入來完成祸穷,這是 Spring IoC 幫助我們自動(dòng)裝配完成的性穿,這樣使得配置大幅度減少,滿足約定優(yōu)于配置的原則雷滚,增強(qiáng)程序的健壯性需曾。

自動(dòng)裝配的歧義性(@Primary和@Qualifier)

在上面的例子中我們使用 @Autowired 注解來自動(dòng)注入一個(gè) Source 類型的 Bean 資源,但如果我們現(xiàn)在有兩個(gè) Srouce 類型的資源,Spring IoC 就會(huì)不知所措呆万,不知道究竟該引入哪一個(gè) Bean:

<bean name="source1" class="pojo.Source">
    <property name="fruit" value="橙子"/>
    <property name="sugar" value="多糖"/>
    <property name="size" value="超大杯"/>
</bean>
<bean name="source2" class="pojo.Source">
    <property name="fruit" value="橙子"/>
    <property name="sugar" value="少糖"/>
    <property name="size" value="小杯"/>
</bean>

我們可以會(huì)想到 Spring IoC 最底層的容器接口——BeanFactory 的定義商源,它存在一個(gè)按照類型獲取 Bean 的方法,顯然通過 Source.class 作為參數(shù)無法判斷使用哪個(gè)類實(shí)例進(jìn)行返回谋减,這就是自動(dòng)裝配的歧義性牡彻。

為了消除歧義性,Spring 提供了兩個(gè)注解:

  • @Primary 注解:
    代表首要的出爹,當(dāng) Spring IoC 檢測(cè)到有多個(gè)相同類型的 Bean 資源的時(shí)候庄吼,會(huì)優(yōu)先注入使用該注解的類。
  • 問題:該注解只是解決了首要的問題严就,但是并沒有選擇性的問題
  • @Qualifier 注解:
    上面所談及的歧義性总寻,一個(gè)重要的原因是 Spring 在尋找依賴注入的時(shí)候是按照類型注入引起的。除了按類型查找 Bean梢为,Spring IoC 容器最底層的接口 BeanFactory 還提供了按名字查找的方法渐行,如果按照名字來查找和注入不就能消除歧義性了嗎?
  • 使用方法: 指定注入名稱為 "source1" 的 Bean 資源
/* 包名和import */
public class JuiceMaker {
    ......
    @Autowired
    @Qualifier("source1")
    public void setSource(Source source) {
        this.source = source;
    }
}

使用@Bean 裝配 Bean

  • 問題: 以上都是通過 @Component 注解來裝配 Bean 铸董,并且只能注解在類上殊轴,當(dāng)你需要引用第三方包的(jar 文件),而且往往并沒有這些包的源碼袒炉,這時(shí)候?qū)o法為這些包的類加入 @Component 注解旁理,讓它們變成開發(fā)環(huán)境中的 Bean 資源。
  • 解決方案:
    1.自己創(chuàng)建一個(gè)新的類來擴(kuò)展包里的類我磁,然后再新類上使用 @Component 注解孽文,但這樣很 low
    2.使用 @Bean 注解,注解到方法之上夺艰,使其成為 Spring 中返回對(duì)象為 Spring 的 Bean 資源芋哭。

我們?cè)?Package【pojo】 下新建一個(gè)用來測(cè)試 @Bean 注解的類:

package pojo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BeanTester {

    @Bean(name = "testBean")
    public String test() {
        String str = "測(cè)試@Bean注解";
        return str;
    }
}
  • 注意: @Configuration 注解相當(dāng)于 XML 文件的根元素,必須要郁副,有了才能解析其中的 @Bean 注解

然后我們?cè)跍y(cè)試類中編寫代碼减牺,從 Spring IoC 容器中獲取到這個(gè) Bean :

// 在 pojo 包下掃描
ApplicationContext context = new AnnotationConfigApplicationContext("pojo");
// 因?yàn)檫@里獲取到的 Bean 就是 String 類型所以直接輸出
System.out.println(context.getBean("testBean"));

@Bean 的配置項(xiàng)中包含 4 個(gè)配置項(xiàng):

  • name: 是一個(gè)字符串?dāng)?shù)組,允許配置多個(gè) BeanName
  • autowire: 標(biāo)志是否是一個(gè)引用的 Bean 對(duì)象存谎,默認(rèn)值是 Autowire.NO
  • initMethod: 自定義初始化方法
  • destroyMethod: 自定義銷毀方法

使用 @Bean 注解的好處就是能夠動(dòng)態(tài)獲取一個(gè) Bean 對(duì)象拔疚,能夠根據(jù)環(huán)境不同得到不同的 Bean 對(duì)象〖燃裕或者說將 Spring 和其他組件分離(其他組件不依賴 Spring稚失,但是又想 Spring 管理生成的 Bean)

Bean 的作用域

在默認(rèn)的情況下,Spring IoC 容器只會(huì)對(duì)一個(gè) Bean 創(chuàng)建一個(gè)實(shí)例恰聘,但有時(shí)候句各,我們希望能夠通過 Spring IoC 容器獲取多個(gè)實(shí)例吸占,我們可以通過 @Scope 注解或者 <bean> 元素中的 scope 屬性來設(shè)置,例如:

// XML 中設(shè)置作用域
<bean id="" class="" scope="prototype" />
// 使用注解設(shè)置作用域
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

Spring 提供了 5 種作用域凿宾,它會(huì)根據(jù)情況來決定是否生成新的對(duì)象:

作用域類別 描述
singleton(單例) 在Spring IoC容器中僅存在一個(gè)Bean實(shí)例 (默認(rèn)的scope)
prototype(多例) 每次從容器中調(diào)用Bean時(shí)矾屯,都返回一個(gè)新的實(shí)例,即每次調(diào)用getBean()時(shí) 初厚,相當(dāng)于執(zhí)行new XxxBean():不會(huì)在容器啟動(dòng)時(shí)創(chuàng)建對(duì)象
request(請(qǐng)求) 用于web開發(fā)件蚕,將Bean放入request范圍 ,request.setAttribute("xxx") 惧所, 在同一個(gè)request 獲得同一個(gè)Bean
session(會(huì)話) 用于web開發(fā)骤坐,將Bean 放入Session范圍,在同一個(gè)Session 獲得同一個(gè)Bean
globalSession(全局會(huì)話) 一般用于 Porlet 應(yīng)用環(huán)境 , 分布式系統(tǒng)存在全局 session 概念(單點(diǎn)登錄)下愈,如果不是 porlet 環(huán)境纽绍,globalSession 等同于 Session

在開發(fā)中主要使用 scope="singleton"scope="prototype"势似,對(duì)于MVC中的Action使用prototype類型拌夏,其他使用singleton,Spring容器會(huì)管理 Action 對(duì)象的創(chuàng)建,此時(shí)把 Action 的作用域設(shè)置為 prototype.

擴(kuò)展閱讀:@Profile 注解 履因、 條件化裝配 Bean

Spring 表達(dá)式語言簡(jiǎn)要說明

Spring 還提供了更靈活的注入方式障簿,那就是 Spring 表達(dá)式,實(shí)際上 Spring EL 遠(yuǎn)比以上注入方式都要強(qiáng)大栅迄,它擁有很多功能:

  • 使用 Bean 的 id 來引用 Bean
  • 調(diào)用指定對(duì)象的方法和訪問對(duì)象的屬性
  • 進(jìn)行運(yùn)算
  • 提供正則表達(dá)式進(jìn)行匹配
  • 集合配置

我們來看一個(gè)簡(jiǎn)單的使用 Spring 表達(dá)式的例子:

package pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("elBean")
public class ElBean {
    // 通過 beanName 獲取 bean站故,然后注入 
    @Value("#{role}")
    private Role role;
    
    // 獲取 bean 的屬性 id
    @Value("#{role.id}")
    private Long id;
    
    // 調(diào)用 bean 的 getNote 方法
    @Value("#{role.getNote().toString()}")
    private String note;
    /* getter and setter */
}

與屬性文件中讀取使用的 “$” 不同,在 Spring EL 中則使用 “#

擴(kuò)展閱讀: Spring 表達(dá)式語言

參考資料:

  • 《Java EE 互聯(lián)網(wǎng)輕量級(jí)框架整合開發(fā)》
  • 《Java 實(shí)戰(zhàn)(第四版)》
  • 萬能的百度 and 萬能的大腦

歡迎轉(zhuǎn)載毅舆,轉(zhuǎn)載請(qǐng)注明出處西篓!
簡(jiǎn)書ID:@我沒有三顆心臟
github:wmyskxz
歡迎關(guān)注公眾微信號(hào):wmyskxz
分享自己的學(xué)習(xí) & 學(xué)習(xí)資料 & 生活
想要交流的朋友也可以加qq群:3382693

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市憋活,隨后出現(xiàn)的幾起案子岂津,更是在濱河造成了極大的恐慌,老刑警劉巖悦即,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吮成,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡辜梳,警方通過查閱死者的電腦和手機(jī)粱甫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冗美,“玉大人魔种,你說我怎么就攤上這事》弁荩” “怎么了节预?”我有些...
    開封第一講書人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)属韧。 經(jīng)常有香客問我安拟,道長(zhǎng),這世上最難降的妖魔是什么宵喂? 我笑而不...
    開封第一講書人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任糠赦,我火速辦了婚禮,結(jié)果婚禮上锅棕,老公的妹妹穿的比我還像新娘拙泽。我一直安慰自己,他們只是感情好裸燎,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開白布顾瞻。 她就那樣靜靜地躺著,像睡著了一般德绿。 火紅的嫁衣襯著肌膚如雪荷荤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,158評(píng)論 1 308
  • 那天移稳,我揣著相機(jī)與錄音蕴纳,去河邊找鬼。 笑死个粱,一個(gè)胖子當(dāng)著我的面吹牛古毛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播都许,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼稻薇,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了梭稚?” 一聲冷哼從身側(cè)響起颖低,我...
    開封第一講書人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎弧烤,沒想到半個(gè)月后忱屑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡暇昂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年莺戒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片急波。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡从铲,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出澄暮,到底是詐尸還是另有隱情名段,我是刑警寧澤阱扬,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站伸辟,受9級(jí)特大地震影響麻惶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜信夫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一窃蹋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧静稻,春花似錦警没、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至恰梢,卻和暖如春佛南,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嵌言。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工嗅回, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人摧茴。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓绵载,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親苛白。 傳聞我的和親對(duì)象是個(gè)殘疾皇子娃豹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)购裙,斷路器懂版,智...
    卡卡羅2017閱讀 134,693評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,838評(píng)論 6 342
  • 文章作者:Tyan博客:noahsnail.com 3.4 Dependencies A typical ente...
    SnailTyan閱讀 4,168評(píng)論 2 7
  • 本章內(nèi)容: 聲明Bean 構(gòu)造器注入和Setter方法注入 裝配Bean 控制bean的創(chuàng)建和銷毀 任何一個(gè)成功的...
    謝隨安閱讀 1,647評(píng)論 0 9
  • 一、學(xué)習(xí)成長(zhǎng) 1躏率、周二周四讀書會(huì) 2躯畴、5.0打卡 3、閱讀《整理家》 4薇芝、參加5.0班晨會(huì) 二蓬抄、體驗(yàn)突破 1、聽宋...
    huhu_b12a閱讀 191評(píng)論 0 0