1. 程式人生 > >六、基於xml的bean例項化和依賴注入

六、基於xml的bean例項化和依賴注入

例項化bean

bean 定義基本上就是用來建立一個或多個物件的配置,當需要一個 bean 的時候,容器檢視配置並且根據 bean 定義封裝的配置元資料建立(或獲取)一個實際的物件。

如果您想要配置的一個bean定義static巢狀類,你必須使用二進位制巢狀類的名稱
舉例來說,如果你有一個叫做類Foo的com.example包,而這個 Foo類有一個static叫做巢狀類Bar,在價值’class’ 上的bean定義屬是…com.example.FooBar使名稱中的字元到巢狀類名從外部類名分開。

JavaBean

JavaBean:是本質就是一個POJO類,但具有一下限制:
1. 該類必須要有公共的無參構造器,如public HelloImpl4() {};
2. 屬性為private訪問級別,不建議public,如private String message;
3. 屬性必要時通過一組setter(修改器)和getter(訪問器)方法來訪問;
4. setter方法,以“set” 開頭,後跟首字母大寫的屬性名,如“setMesssage”,簡單屬性一般只有一個方法引數,方法返回值通常為“void”;
5. getter方法,一般屬性以“get”開頭,對於boolean型別一般以“is”開頭,後跟首字母大寫的屬性名,如“getMesssage”,“isOk”;
6. 還有一些其他特殊情況,比如屬性有連續兩個大寫字母開頭,如“URL”,則setter/getter方法為:“setURL”和“getURL”,其他一些特殊情況請參看“Java Bean”命名規範。

Spring具有非常大的靈活性,它提供了三種主要的裝配機制
1. 通過配置檔案xml類提供。
2. 通過註解。bean的建立預設是採用無參的構造方法進行初始化。
3. 通過@bean註解的方法。

建立bean的三種方式

1. 通過建構函式例項化

BeanExample

package com.lf.bean;

/**
 * Created by LF on 2017/4/6.
 */
public class BeanExample {
    private String name;

    private int age;

    private boolean student;

    public
String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public boolean isStudent() { return student; } public
void setStudent(boolean student) { this.student = student; } }

當使用基於XML的元資料配置檔案,可以這樣來指定 bean 類

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

    <bean id="exampleBean" class="com.lf.bean.BeanExample"></bean>

</beans>

2.使用靜態工廠方法例項化

當採用靜態工廠方法建立 bean 時,除了需要指定 class 屬性外,還需要通過 factory-method 屬性來指定建立 bean 例項的工廠方法。Spring將呼叫此方法(其可選引數接下來介紹)返回例項物件,就此而言,跟通過普通構造器建立類例項沒什麼兩樣。

下面的 bean 定義展示瞭如何通過工廠方法來建立bean例項。注意,此定義並未指定返回物件的型別,僅指定該類包含的工廠方法。在此例中,createInstance() 必須是一個 static 方法。

package com.lf.bean;

/**
 * Created by LF on 2017/4/6.
 */
public class ClientService {
    private static ClientService clientService = new ClientService();

    private ClientService() {
    }

    public static ClientService createInstance() {
        return clientService;
    }
}
  <bean class="com.lf.bean.ClientService" factory-method="createInstance"></bean>

3.使用例項工廠方法例項化

與通過 靜態工廠方法 例項化類似,通過呼叫工廠例項的非靜態方法進行例項化。 使用這種方式時,class屬性必須為空,而factory-bean屬性必須指定為當前(或其祖先)容器中包含工廠方法的bean的名稱,而該工廠bean的工廠方法本身必須通過
factory-method屬性來設定。

package com.lf.service;


/**
 * Created by LF on 2017/4/6.
 */
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();
    private static AccountService accountService = new AccountServiceImpl();

    private DefaultServiceLocator() {
    }

    public ClientService createClientServiceInstance() {
        return clientService;
    }

    public AccountService createAccountServiceInstance() {
        return accountService;
    }
}
    <bean id="serviceLocator" class="com.lf.service.DefaultServiceLocator">
        <!-- 其他需要注入的依賴項 -->
    </bean>
    <bean id="clientService"
          factory-bean="serviceLocator"
          factory-method="createClientServiceInstance"/>
    <bean id="accountService"
          factory-bean="serviceLocator"
          factory-method="createAccountServiceInstance"/>

依賴注入

依賴注入(DI)是一種方法,其中物件之間的依存關係,即,被其構造後的物件例項設定或返回它們一起工作,僅通過構造引數的其它的目的,引數工廠方法或屬性從工廠方法。然後將容器注入那些依賴關係時,它建立的bean。此過程基本上與逆,因此得名控制反轉(IOC),bean本身使用的類直接施工,或者控制其自身的相關性的例項化或位置的服務定位器模式。

使用DI原理,程式碼更清晰,當物件具有依賴關係時,解耦更有效。物件不查詢其依賴關係,並且不知道依賴關係的位置或類。因此,您的類變得更容易測試,特別是當依賴關係在介面或抽象基類上時,允許在單元測試中使用存根或模擬實現。

DI主要有兩種變型,基於建構函式的依賴注入和基於setter方法的依賴注入。

1. 基於建構函式的依賴注入

當通過建構函式方法建立一個bean時,所有正常類都可以使用並與Spring相容。也就是說,正在開發的類不需要實現任何特定的介面或以特定方式編碼。只需指定bean類即可。但是,根據特定bean使用的IoC型別,您可能需要一個預設(空)建構函式。

Spring IoC容器可以管理幾乎任何你想它來管理類; 它不限於管理真正的JavaBeans。大多數Spring使用者喜歡實際的JavaBean,只有預設(無引數)建構函式和容器中的屬性之後建立適當的setter和getter。您還可以在容器中擁有更多異國情調的非Bean風格的類。例如,如果您需要使用絕對不遵守JavaBean規範的舊版連線池,那麼Spring也可以管理它。

構造器注入可以根據引數索引注入、引數型別注入或Spring3支援的引數名注入,但引數名注入是有限制的,需要使用在編譯程式時開啟除錯模式(即在編譯時使用“javac –g:vars”在class檔案中生成變數除錯資訊,預設是不包含變數除錯資訊的,從而能獲取引數名字,否則獲取不到引數名字)或在構造器上使用@ConstructorProperties(java.beans.ConstructorProperties)註解來指定引數名

package com.lf.bean;

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

/**
 * Created by LF on 2017/4/6.
 */
public class Example {

    private String name;

    private int age;

    private boolean student;

    private List<String> roles;

    private Map<String, String> map;

    private Set<String> set;

    public Example() {
    }

    public Example(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Example(List<String> roles) {
        this.roles = roles;
    }

    public Example(Map<String, String> map) {
        this.map = map;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public boolean isStudent() {
        return student;
    }

    public void setStudent(boolean student) {
        this.student = student;
    }

    public List<String> getRoles() {
        return roles;
    }

    public void setRoles(List<String> roles) {
        this.roles = roles;
    }

    public Map<String, String> getMap() {
        return map;
    }

    public void setMap(Map<String, String> map) {
        this.map = map;
    }

    public Set<String> getSet() {
        return set;
    }

    public void setSet(Set<String> set) {
        this.set = set;
    }

    @Override
    public String toString() {
        return "Example{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", student=" + student +
                ", roles=" + roles +
                ", map=" + map +
                ", set=" + set +
                '}';
    }
}

一、根據引數索引注入

使用標籤<constructor-arg index="1" value="1"/>來指定注入的依賴,其中“index”表示索引,從0開始,即第一個引數索引為0,“value”來指定注入的常量值,配置方式如下:

<!-- 通過構造器引數索引方式依賴注入 -->

    <bean id="exampleByIndex" class="com.lf.bean.Example">
        <constructor-arg index="0" value="張三"></constructor-arg>
        <constructor-arg index="1" value="10"></constructor-arg>
    </bean>

二、根據引數型別進行注入

使用標籤<constructor-arg type="java.lang.String" value="Hello World!"/>來指定注入的依賴,其中“type”表示需要匹配的引數型別,可以是基本型別也可以是其他型別,如“int”、“java.lang.String”,“value”來指定注入的常量值,配置方式如下:

  <!-- 通過構造器引數型別方式依賴注入 -->
    <bean id="exampleByType" class="com.lf.bean.Example">
        <constructor-arg type="java.lang.String" v  value="張三"></constructor-arg>
        <constructor-arg value="10"></constructor-arg>
    </bean>

三、根據引數名進行注入

使用標籤<constructor-arg name="message" value="Hello World!"/>來指定注入的依賴,其中“name”表示需要匹配的引數名字,“value”來指定注入的常量值,配置方式如下:


<!-- 通過構造器引數名稱方式依賴注入 -->

    <bean id="exampleByName" class="com.lf.bean.Example">
        <constructor-arg name="name" value="張三"></constructor-arg>
        <constructor-arg name="age" value="10"></constructor-arg>
    </bean>

最基本的物件建立方式,只需要有一個無參建構函式(類中沒有寫任何的建構函式,預設就是有一個建構函式,如果寫了任何一個建構函式,預設的無參建構函式就不會自動建立)和欄位的setter方法。其本質為:SpringContext利用無參的建構函式建立一個物件,然後利用setter方法賦值。所以如果無參建構函式不存在,Spring上下文建立物件的時候便會報錯。

2. 靜態工廠方法

靜態工廠類

//靜態工廠類
public class DependencyInjectByStaticFactory {
    public static HelloApi newInstance(String message, int index) {
        return new HelloImpl3(message, index);
    }
}
<bean id="byIndex" 
class="cn.javass.spring.chapter3.DependencyInjectByStaticFactory" factory-method="newInstance">
<constructor-arg index="0" value="Hello World!"/>
<constructor-arg index="1" value="1"/>
</bean> 
<bean id="byType" 
class="cn.javass.spring.chapter3.DependencyInjectByStaticFactory" factory-method="newInstance">
<constructor-arg type="java.lang.String" value="Hello World!"/>
    <constructor-arg type="int" value="2"/>
</bean> 
<bean id="byName" 
class="cn.javass.spring.chapter3.DependencyInjectByStaticFactory" factory-method="newInstance">
<constructor-arg name="message" value="Hello World!"/>
    <constructor-arg name="index" value="3"/>
    </bean>

例項工廠類

//例項工廠類
package cn.javass.spring.chapter3;
import cn.javass.spring.chapter2.helloworld.HelloApi;
public class DependencyInjectByInstanceFactory {
    public HelloApi newInstance(String message, int index) {
        return new HelloImpl3(message, index);
    }
}
<bean id="instanceFactory" 
class="cn.javass.spring.chapter3.DependencyInjectByInstanceFactory"/>

<bean id="byIndex" 
      factory-bean="instanceFactory"  factory-method="newInstance">
    <constructor-arg index="0" value="Hello World!"/>
    <constructor-arg index="1" value="1"/>
</bean> 

<bean id="byType" 
    factory-bean="instanceFactory" factory-method="newInstance">
    <constructor-arg type="java.lang.String" value="Hello World!"/>
    <constructor-arg type="int" value="2"/>
</bean> 

<bean id="byName"
    factory-bean="instanceFactory" factory-method="newInstance">
    <constructor-arg name="message" value="Hello World!"/>
    <constructor-arg name="index" value="3"/>
</bean> 

3. 基於Setter的依賴注入

setter注入,是通過在通過構造器、靜態工廠或例項工廠例項好Bean後,通過呼叫Bean類的setter方法進行注入依賴

      <bean id="exampleBySet" class="com.lf.bean.Example">
        <property name="name" value="張三"></property>
        <property name="age" value="10"></property>
    </bean>

在Spring團隊主張建構函式注入,因為它使一個實現應用程式元件不可變物件,並確保所需的依賴不是null。此外,建構函式注入的元件總是以完全初始化的狀態返回給客戶端(呼叫)程式碼。作為一個方面說明,大量的建構函式的引數是一個不好的程式碼的氣味,這意味著該類可能有太多的責任,應該重構,以更好地解決關切適當分離。

Setter注入應主要用於可選依賴關係,可以在類中分配合理的預設值。否則,必須在程式碼使用依賴關係的任何地方執行非空檢查。setter注入的一個好處是setter方法使得該類的物件可以在以後重新配置或重新注入。通過管理JMX的MBean因此是一個引人注目的用例setter注入。

使用對特定課程最有意義的DI風格。有時候,當您處理沒有來源的第三方課程時,您可以選擇。例如,如果第三方類不暴露任何setter方法,那麼建構函式注入可能是DI的唯一可用形式。

setter注入方式只有一種根據setter名字進行注入:

4. 注入常量

注入常量是依賴注入中最簡單的。配置方式如下所示:

<property name="message" value="Hello World!"/><property name="index"><value>1</value></property>

5. 注入bean ID

用於注入Bean的ID,ID是一個常量不是引用,且類似於注入常量,但提供錯誤驗證功能,配置方式如下所示:

Paste_Image.png
第一種方式可以在容器初始化時校驗被引用的Bean是否存在,如果不存在將丟擲異常,而第二種方式只有在Bean實際使用時才能發現傳入的Bean的ID是否正確,可能發生不可預料的錯誤。

6. 注入集合、陣列和字典

Spring不僅能注入簡單型別資料,還能注入集合(Collection、無序集合Set、有序集合List)型別、陣列(Array)型別、字典(Map)型別資料、Properties型別資料,接下來就讓我們一個個看看如何注入這些資料型別的資料。
一、注入集合型別:包括Collection型別、Set型別、List型別資料:
(1)List型別:需要使用標籤來配置注入,其具體配置如下:

       <bean id="list" class="com.lf.bean.Example">
        <constructor-arg name="roles">
            <list>
                <value>1</value>
                <value>2</value>
                <value>3</value>
            </list>
        </constructor-arg>
    </bean>

(2)Set型別:需要使用標籤來配置注入,其配置引數及含義和標籤完全一樣


    <bean id="set" class="com.lf.bean.Example">
        <constructor-arg name="set">
            <set>
                <value>1</value>
                <value>2</value>
                <value>3</value>
            </set>
        </constructor-arg>
    </bean>

(3)Collection型別:因為Collection型別是Set和List型別的基型別,所以使用<set><list>標籤都可以進行注入,配置方式完全和以上配置方式一樣,只是將測試類屬性改成“Collection”型別,
二、注入陣列型別:需要使用<array>標籤來配置注入,其中標籤屬性“value-type”和“merge”和標籤含義完全一樣


    <bean id="arrays" class="com.lf.bean.Example">
        <constructor-arg name="arrays">
          <array>
                <value>1</value>
                <value>2</value>
                <value>3</value>
          </array>
        </constructor-arg>
    </bean>

三、注入字典(Map)型別:字典型別是包含鍵值對資料的資料結構,需要使用<map>標籤來配置注入,其屬性“key-type”和“value-type”分別指定“鍵”和“值”的資料型別,其含義和<list>標籤的“value-type”含義一樣,在此就不羅嗦了,並使用<key>子標籤來指定鍵資料,<value>子標籤來指定鍵對應的值資料,具體配置如下:


    <bean id="map" class="com.lf.bean.Example">
        <constructor-arg name="map">
            <map key-type="java.lang.String" value-type="java.lang.String">
                <entry key="1" value="值"></entry>
                <entry key="2" value="值2"></entry>
            </map>
        </constructor-arg>
    </bean>

四、Properties注入:Spring能注入java.util.Properties型別資料,需要使用標籤來配置注入,鍵和值型別必須是String,不能變,子標籤值來指定鍵值對,具體配置如下


    <bean id="properties" class="com.lf.bean.Example">
        <constructor-arg name="properties">
           <props>
               <prop key="1" >值1</prop>
               <prop key="2" >值2</prop>
           </props>
        </constructor-arg>
    </bean>

以上兩種方式都可以,從配置來看第一種更簡潔。注意此處“value”中指定的全是字串,由Spring容器將此字串轉換成屬性所需要的型別,如果轉換出錯,將丟擲相應的異常。Spring容器目前能對各種基本型別把配置的String引數轉換為需要的型別。
注:Spring型別轉換系統對於boolean型別進行了容錯處理,除了可以使用“true/false”標準的Java值進行注入,還能使用“yes/no”、“on/off”、“1/0”來代表“真/假”,

定義掃描的包

<context:component-scan 
        base-package="" 
resource-pattern="**/*.class"
        name-generator="
org.springframework.context.annotation.AnnotationBeanNameGenerator"
        use-default-filters="true"
annotation-config="true">
      <context:include-filter type="aspectj" expression=""/>
      <context:exclude-filter type="regex" expression=""/>
    </context:component-scan>   

 base-package:表示掃描註解類的開始位置,即將在指定的包中掃描,其他包中的註解類將不被掃描,預設將掃描所有類路徑;
 resource-pattern:表示掃描註解類的字尾匹配模式,即“base-package+resource-pattern”將組成匹配模式用於匹配類路徑中的元件,預設字尾為“*/.class”,即指定包下的所有以.class結尾的類檔案;
 name-generator:預設情況下的Bean識別符號生成策略,預設是AnnotationBeanNameGenerator,其將生成以小寫開頭的類名(不包括包名);可以自定義自己的識別符號生成策略;
 use-default-filters:預設為true表示過濾@Component、@ManagedBean、@Named註解的類,如果改為false預設將不過濾這些預設的註解來定義Bean,即這些註解類不能被過濾到,即不能通過這些註解進行Bean定義;
 annotation-config:表示是否自動支援註解實現Bean依賴注入,預設支援,如果設定為false,將關閉支援註解的依賴注入,需要通過開啟。
預設情況下將自動過濾@Component、@ManagedBean、@Named註解的類並將其註冊為Spring管理Bean,可以通過在標籤中指定自定義過濾器將過濾到匹配條件的類註冊為Spring管理Bean,具體定義方式如下:

<context:include-filter type="aspectj" expression=""/>
<context:exclude-filter type="regex" expression=""/>

 :表示過濾到的類將被註冊為Spring管理Bean;
 :表示過濾到的類將不被註冊為Spring管理Bean,它比具有更高優先順序;
 type:表示過濾器型別,目前支援註解型別、類型別、正則表示式、aspectj表示式過濾器,當然也可以自定義自己的過濾器,實現org.springframework.core.type.filter.TypeFilter即可;
 expression:表示過濾器表示式。
一般情況下沒必要進行自定義過濾,如果需要請參考如下示例:

1、cn.javass.spring.chapter12.TestBean14自動註冊為Spring管理Bean:

<context:include-filter type="assignable" 
expression="cn.javass.spring.chapter12.TestBean14"/>

2、把所有註解為org.aspectj.lang.annotation.Aspect自動註冊為Spring管理Bean:

<context:include-filter type="annotation" 
expression="org.aspectj.lang.annotation.Aspect"/>

3、將把匹配到正則表示式“cn.javass.spring.chapter12.TestBean2*”排除,不註冊為Spring管理Bean:

<context:exclude-filter type="regex"
 expression="cn\.javass\.spring\.chapter12\.TestBean2*"/>

4、將把匹配到aspectj表示式“cn.javass.spring.chapter12.TestBean3*”排除,不註冊為Spring管理Bean:

<context:exclude-filter type="aspectj"
 expression="cn.javass.spring.chapter12.TestBean3*"/>

具體使用就要看專案需要了,如果以上都不滿足需要請考慮使用自定義過濾器。