1、回顧依賴注入的三種方式
在前面第三章中(Spring詳解(三)——認識IoC控制反轉/DI依賴注入)介紹了什么是依賴注入和它們的簡單應用,它有3種方式:
- 構造器注入
- setter方法注入
- 接口注入
其中構造器注入和setter注入是最主要的方式,下面進行簡單回顧一下。
①、構造器注入:顧名思義就是被注入對象可以通過在其構造方法中聲明依賴對象的參數列表,讓外部(通常是IoC容器)知道它需要哪些依賴對象。
在大部分的情況下,我們都是通過類的構造方法來創建類對象, Spring 也可以采用反射的方式, 通過使用構造方法來完成注入,這就是構造器注入的原理。
首先要創建一個具體的類、構造方法並設置對應的參數,這里以User為例:
/** * 用戶實體類 */ public class User { private int userId; private String userName; private int userAge; private String userPwd; private String userAddress; //getter、setter、toString方法省略...... //有參構造器 public User(int userId, String userName, int userAge, String userPwd, String userAddress) { this.userId = userId; this.userName = userName; this.userAge = userAge; this.userPwd = userPwd; this.userAddress = userAddress; } }
如果我們在實體類中創建了有參的構造器,而沒有顯示的創建無參構造器,那么是不能再通過無參的構造器創建對象了,為了使 Spring 能夠正確創建這個對象,可以像如下Spring配置去做。
<?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"> <!--將指定類都配置給Spring,讓Spring創建其對象的實例,一個bean對應一個對象 如果類中創建了有參構造器,必須完成初始化--> <bean id="user" class="com.thr.pojo.User"> <constructor-arg index="0" value="2020"/> <constructor-arg index="1" value="菜逼唐"/> <constructor-arg index="2" value="18"/> <constructor-arg index="3" value="123456"/> <constructor-arg index="4" value="地球中國"/> </bean> </beans>
constructor-arg元素用於定義類構造方法的參數,其中index 用於定義參數的位置(從0開始),而 value 則是設置值,通過這樣的定義 Spring 便知道使用 哪個構造方法去創建對象了。雖然這樣注入還是比較簡單的,但是缺點也很明顯,由於這里的參數比較少,所以可讀性還是不錯的,但是如果參數很多,那么這種構造方法就比較復雜了,這個時候應該考慮 setter 注入。
②、setter方法注入:setter 注入是 Spring 中最主流的注入方式,它利用 Java Bean 規范所定義的 setter 方法來完成注入,靈活且可讀性高。它消除了使用構造器注入時出現多個參數的可能性,首先可以把構造方法聲明為無參數的,然后使用 setter 注入為其設置對應的值,其實也是通過 Java 反射技術得以現實的。這里去掉上面User類中的有參數的構造方法,然后做如下的Spring配置。
<bean id="user1" class="com.thr.pojo.User"> <property name="userId" value="2020"/> <property name="userName" value="菜逼唐"/> <property name="userAge" value="18"/> <property name="userPwd" value="123456"/> <property name="userAddress" value="地球中國"/> </bean>
這樣Spring就會通過反射調用沒有參數的構造方法生成對象,同時通過反射對應的setter注入配置的值了。這種方式是Spring最主要的方式,在實際的工作中是最常用的,所以下面都是基於setter方法注入的舉例。
③、接口注入:接口注入是現在非常不提倡的一種方式,這種方式基本處於“退役狀態”。因為它強制被注入對象實現不必要的接口,帶有侵入性。而構造方法注入和setter方法注入則不需要如此,所以現在我們一般推薦使用構造器注入和setter注入。
2、裝配 Bean 概述
說簡單點就是將自己開發的 Bean 裝配到 Spring IoC 容器中。在 Spring 中提供了3種方法進行配置:
- XML 中顯示配置(Spring的XML配置文件)
- Java 的接口和類中實現配置(比如:@Configuration+@Bean) 這種方式在后面的SpringBoot中會經常使用,所以推薦使用這種方式,主要是這種方式不用去寫配置文件了
- 隱式 Bean 的發現機制和自動裝配原則(比如:@Component+@Autowired)
在現實的工作中,這3 種方式都會被用到,並且在學習和工作中常常混合使用,所以我們需要明確3種方式的優先級,也就是我們應該怎么選擇使用哪種方式去把 Bean 發布到 Spring IoC 容器中。所以這里給出關於這 3 種方法優先級的建議(優先級從高到低):
- 基於約定優於配置的原則,最優先的應該是通過隱式 Bean 發現機制和自動裝配的原則。這樣的好處是減少程序開發者的決定權,簡單又不失靈活,所以這種方式在我們的實際開發中用的最多。
- 在沒有辦法使用自動裝配原則的情況下應該優先考慮 Java 接口和類中實現配置,這樣的好處是避免 XML 置的泛濫,也更為容易 。這種場景典型的例子是 一個父類有多個子類,比如學生類有兩個子類,一個男學生類和女學生類,通過 IoC 容器初始化一個學生類,容器將無法知道使用哪個子類去初始化,這個時候可以使用 Java 的注解配置去指定。
- 如果上述的兩種方法都無法使用的情況下,那么只能選擇 XML 去配置 Spring IoC 容器。這種方式的好處就是簡單易懂,對於初學者非常友好。這種場景的例子是由於現實工作中常常用到第三方的類庫,有些類並不是我們開發的,我們無法修改里面的代碼,這個時候就通過 XML 方式配置使用了。
通俗來講,當配置的類是你自身正在開發的工程,那么應該考慮 Java 配置為主,而 Java 配置又分為自動裝配和 Bean 名稱配置。在沒有歧義的基礎上,優先使用自動裝配,這樣就可以減少大量的 XML 置。如果所需配置的類並不是你的工程開發的,那么建議使用 XML 的方式。
本章都是通過 XML 的方式來配置 Bean,這樣會更好的理解。使用 XML 裝配 Bean 需要定義對應的 XML,這里需要引入對應的 XML 模式(XSD)文件,這些文件會定義配置 Spring Bean 的一些元素。
這樣我們就可以在里面定義對應的 Spring Bean了。
3、Bean 裝配簡單值
這里先來討論最簡單的裝配,比如基本的屬性和對象,代碼如下:
/** * 用戶實體類 */ public class User { private int userId; private String userName; private int userAge; private String userPwd; private String userAddress; //女朋友 private GirlFriend girlFriend; //getter、setter、toString方法省略...... }
GirlFriend實體:
/** * GirlFriend實體 */ public class GirlFriend { private String girlName; private int girlAge; private String girlHeight; //getter、setter、toString方法省略...... }
Spring的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"> <!--實例化GirlFriend--> <bean id="girlFriend" class="com.thr.pojo.GirlFriend"> <property name="girlName" value="王美麗"/> <property name="girlAge" value="18"/> <property name="girlHeight" value="170"/> </bean> <!--實例化User--> <bean id="user1" class="com.thr.pojo.User"> <!--注入普通值:使用 value 屬性--> <property name="userId" value="2020"/> <property name="userName" value="菜逼唐"/> <property name="userAge" value="18"/> <property name="userPwd" value="123456"/> <property name="userAddress" value="地球中國"/> <!--注入對象:使用 ref 屬性--> <property name="girlFriend" ref="girlFriend"/> </bean> </beans>
上面就是一個最簡單最基本的配置Bean了,這里簡單來解釋一下:
- id 屬性是標識符(別名),用來讓Spring找到這個Bean,id屬性不是一個必須的屬性,如果我們沒有聲明它,那么 Spring 將會采用“全限定名#{number}“的格式生成編號。例如這里,如果沒有聲明 “id="user"的話,那么 Spring 為其生成的編號就是"com.thr.pojo.User#0”,當它第二次聲明沒有 id 屬性的 Bean 時,編號就是"com.thr.pojo.User#1",后面以此類推。但是我們一般都會顯示聲明自定義的id,因為自動生成的id比較繁瑣,不便於維護。
- class 屬性顯然就是一個類的全限定名 。
- property 元素是定義類的屬性,其中的 name 屬性定義的是屬性的名稱,而 value 是它的值,ref 是用來引入對象的。
簡單來測試一下,測試代碼如下:
public class SpringTest { public static void main(String[] args) { //1.初始化Spring容器,加載配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); //2.通過容器獲取實例,getBean()方法中的參數是bean標簽中的id User user = applicationContext.getBean("user1", User.class); //3.調用實例中的屬性 System.out.println(user.getUserName()+"------"+user.getGirlFriend()); } }
運行結果:
最后注意:注入基本值使用 value 屬性,注入對象使用 ref 屬性。
4、Bean 裝配集合
有些時候我們需要裝配一些復雜的Bean,比如 Set、Map、List、Array 和 Properties 等,所以我們將上面的User改一下,假如這個User是個“海王”呢?他有好幾個GirlFriend。我們對User類添加了一些屬性(記得更改setter、getter和tostring方法):
/** * 用戶實體類 */ public class User { private int userId; private String userName; private int userAge; private String userPwd; private String userAddress; //女朋友 private GirlFriend girlFriend; private List<GirlFriend> lists; private Set<GirlFriend> sets; private Map<String, GirlFriend> maps; private Properties properties; private String[] array; //getter、setter、toString方法省略...... }
Spring的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"> <!--實例化GirlFriend--> <bean id="girlFriend1" class="com.thr.pojo.GirlFriend"> <property name="girlName" value="王美麗"/> <property name="girlAge" value="18"/> <property name="girlHeight" value="170"/> </bean> <bean id="girlFriend2" class="com.thr.pojo.GirlFriend"> <property name="girlName" value="楊美麗"/> <property name="girlAge" value="19"/> <property name="girlHeight" value="171"/> </bean> <bean id="girlFriend3" class="com.thr.pojo.GirlFriend"> <property name="girlName" value="李美麗"/> <property name="girlAge" value="20"/> <property name="girlHeight" value="172"/> </bean> <!--實例化User--> <bean id="user2" class="com.thr.pojo.User"> <!--注入普通值:使用 value 屬性--> <property name="userId" value="2020"/> <property name="userName" value="菜逼唐"/> <property name="userAge" value="18"/> <property name="userPwd" value="123456"/> <property name="userAddress" value="地球中國"/> <!--注入對象:使用 ref 屬性--> <property name="girlFriend" ref="girlFriend1"/> <!--注入List集合--> <property name="lists"> <list> <ref bean="girlFriend1"/> <ref bean="girlFriend2"/> <ref bean="girlFriend3"/> </list> </property> <!--注入Set集合--> <property name="sets"> <set> <ref bean="girlFriend1"/> <ref bean="girlFriend2"/> <ref bean="girlFriend3"/> </set> </property> <!--注入Map集合--> <property name="maps"> <map> <entry key="正牌女友" value-ref="girlFriend1"/> <entry key="備胎1" value-ref="girlFriend2"/> <entry key="備胎2" value-ref="girlFriend3"/> </map> </property> <!--注入Properties--> <property name="properties"> <props> <prop key="k1">v1</prop> <prop key="k2">v2</prop> </props> </property> <!--注入數組--> <property name="array"> <array> <value>value1</value> <value>value2</value> <value>value3</value> </array> </property> </bean> </beans>
對集合的裝配進行總結:
- List 屬性使用對應的 <list> 元素進行裝配,然后通過多個 <value> 元素設值,如果是bean則通過<ref>元素設值。
- Set 屬性使用對應的 <set> 元素進行裝配,然后通過多個 <value> 元素設值,如果是bean則通過<ref>元素設值。
- Map 屬性使用對應的 <map> 元素進行裝配,然后通過多個 <entry> 元素設值,entry 中包含一個鍵值對(key-value)的設置,普通值使用key和value,bean使用key-ref和value-ref設值。
- Properties 屬性使用對應的 <properties> 元素進行裝配,通過多個 <property> 元素設值,只是 properties 元素有一個必填屬性 key ,然后可以設置值
- 對於數組而言,可以使用 <array> 設置值,然后通過多個 <value> 元素設值。
簡單來測試一下,測試代碼如下:
public class SpringTest { public static void main(String[] args) { //1.初始化Spring容器,加載配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); //2.通過容器獲取實例,getBean()方法中的參數是bean標簽中的id User user = applicationContext.getBean("user2", User.class); //3.調用實例中的屬性 System.out.println("List集合:"+user.getLists()); System.out.println("Set集合:"+user.getSets()); System.out.println("Map集合:"+user.getMaps()); System.out.println("Properties:"+user.getProperties()); System.out.println("數組:"); String[] array = user.getArray(); for (String s : array) { System.out.println(s); } } }
運行結果:
5、命名空間裝配 Bean(了解)
除了使用上述的的方法來裝配Bean之外,Spring還提供了對應的命名空間的定義。
- c 命名空間:用於通過構造器注入的方式來配置 bean
- p 命名空間:用於通過setter的注入方式來配置 bean
- util 命名空間:工具類的命名空間,可以簡化集合類元素的配置
下面來簡單介紹。要使用它們首先得猶如對應的命名空間和XML模式(XSD)文件。
示例代碼:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <!--c 命名空間 實例化GirlFriend,給GirlFriend顯示的創建了一個無參和有參構造器--> <bean id="girlFriend1" class="com.thr.pojo.GirlFriend" c:_0="王美麗" c:_1="18" c:_2="170"/> <!--p 命名空間 實例化GirlFriend--> <bean id="girlFriend2" class="com.thr.pojo.GirlFriend" p:girlName="楊美麗" p:girlAge="20" p:girlHeight="168"/> <!--util 命名空間--> <!--List集合--> <util:list id="lists"> <ref bean="girlFriend1"/> <ref bean="girlFriend2"/> </util:list> <!--Set集合--> <util:set id="sets"> <ref bean="girlFriend1"/> <ref bean="girlFriend2"/> </util:set> <!--Map集合--> <util:map id="maps"> <entry key="第一個女友" value-ref="girlFriend1"/> <entry key="第二個女友" value-ref="girlFriend2"/> </util:map> <!--Properties集合--> <util:properties id="properties"> <prop key="k1">v1</prop> </util:properties> <!--實例化User--> <bean id="user3" class="com.thr.pojo.User" p:userId="2020" p:userName="菜逼唐" p:userAge="18" p:userPwd="123456" p:userAddress="地球中國" p:girlFriend-ref="girlFriend1" p:lists-ref="lists" p:sets-ref="sets" p:maps-ref="maps" p:properties-ref="properties"> </bean> </beans>
總結:
- c 命名空間:用於通過構造器注入的方式來配置 bean,c:_0 表示構造方法的第一個參數,c:_1 表示構造方法的第而個參數,以此類推。
- p 命名空間:用於通過setter的注入方式來配置 bean,p:屬性名 表示為屬性設值,p:list-ref 表示采用List屬性,引用其上下文對應好的Bean,這里顯然是util命名空間定義的List,Map和Set同理。
- util 命名空間:工具類的命名空間,可以簡化集合類元素的配置。下表提供了 util-命名空間提供的所有元素:
util元素 | 描述 |
<util:constant> | 引用某個類型的 public static 域,並將其暴露為 bean |
<util:list> | 創建一個 java.util.List 類型的 bean,其中包含值或引用 |
<util:map> | 創建一個 java.util.map 類型的 bean,其中包含值或引用 |
<util:properties> | 創建一個 java.util.Properties 類型的 bean |
<util:property-path> | 引用一個 bean 的屬性(或內嵌屬性),並將其暴露為 bean |
<util:set> | 創建一個 java.util.Set 類型的 bean,其中包含值或引用 |
參考資料:
- 《Java EE 互聯網輕量級框架整合開發》
- 《Spring 實戰》(第四版)