六、基於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是一個常量不是引用,且類似於注入常量,但提供錯誤驗證功能,配置方式如下所示:
第一種方式可以在容器初始化時校驗被引用的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*"/>
具體使用就要看專案需要了,如果以上都不滿足需要請考慮使用自定義過濾器。