spring成神之路第七篇:DI依賴注入之手動注入
本文內容
-
主要介紹xml中依賴注入的配置
-
構造器注入的3種方式詳解
-
set方法注入詳解
-
注入容器中的其他bean的2種方式
-
其他常見型別注入詳解
依賴回顧
通常情況下,系統中類和類之間是有依賴關係的,如果一個類對外提供的功能需要通過呼叫其他類的方法來實現的時候,說明這兩個類之間存在依賴關係,如:
publicclassUserService{
publicvoidinsert(UserModelmodel){
//插入使用者資訊
}
}
publicclassUserController{
privateUserServiceuserService;
publicvoidinsert(UserModelmodel) {
this.userService.insert(model);
}
}
UserController中的insert方法中需要呼叫userService的insert方法,說明UserController依賴於UserService,如果userService不存在,此時UserControler無法對外提供insert操作。
那麼我們建立UserController物件的時候如何將給userService設定值呢?通常有2種方法。
依賴物件的初始化方式
通過構造器設定依賴物件
UserController中新增一個有參構造方法,如下:
publicclassUserController{
private UserServiceuserService;
publicUserController(UserServiceuserService){
this.userService=userService;
}
publicvoidinsert(UserModelmodel){
this.userService.insert(model);
}
}
//UserController使用
UserSerivceuserService=newUserService();
UserControlleruserController=newUserController(userService);
//然後就可以使用userController物件了
通過set方法設定依賴物件
可以在UserController中給userService新增一個set方法,如:
publicclassUserController{
privateUserServiceuserService;
publicsetUserService(UserServiceuserService){
this.userService=userService;
}
publicvoidinsert(UserModelmodel){
this.userService.insert(model);
}
}
//UserController使用
UserSerivceuserService=newUserService();
UserControlleruserController=newUserController();
userController.setService(userService);
//然後就可以使用userController物件了
上面這些操作,將被依賴的物件設定到依賴的物件中,spring容器內部都提供了支援,這個在spirng中叫做依賴注入。
spring依賴注入
spring中依賴注入主要分為手動注入和自動注入,本文我們主要說一下手動注入,手動注入需要我們明確配置需要注入的物件。
剛才上面我們回顧了,將被依賴方注入到依賴方,通常有2種方式:建構函式的方式和set屬性的方式,spring中也是通過這兩種方式實現注入的,下面詳解2種方式。
通過構造器注入
構造器的引數就是被依賴的物件,構造器注入又分為3種注入方式:
-
根據構造器引數索引注入
-
根據構造器引數型別注入
-
根據構造器引數名稱注入
根據構造器引數索引注入
用法
<beanid="diByConstructorParamIndex"class="com.javacode2018.lesson001.demo5.UserModel">
<constructor-argindex="0"value="路人甲Java"/>
<constructor-argindex="1"value="上海市"/>
</bean>
constructor-arg使用者指定構造器的引數
index:構造器引數的位置,從0開始
value:構造器引數的值,value只能用來給簡單的型別設定值,value對應的屬性型別只能為byte,int,long,float,double,boolean,Byte,Long,Float,Double,列舉,spring容器內部注入的時候會將value的值轉換為對應的型別。
案例
UserModel.java
packagecom.javacode2018.lesson001.demo5;
publicclassUserModel{
privateStringname;
privateintage;
//描述資訊
privateStringdesc;
publicUserModel(){
}
publicUserModel(Stringname,Stringdesc){
this.name=name;
this.desc=desc;
}
publicUserModel(Stringname,intage,Stringdesc){
this.name=name;
this.age=age;
this.desc=desc;
}
@Override
publicStringtoString(){
return"UserModel{"+
"name='"+name+'\''+
",age="+age+
",desc='"+desc+'\''+
'}';
}
}
注意上面的3個建構函式。
diByConstructorParamIndex.xml
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="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-4.3.xsd">
<!--通過構造器引數的索引注入-->
<beanid="diByConstructorParamIndex"class="com.javacode2018.lesson001.demo5.UserModel">
<constructor-argindex="0"value="路人甲Java"/>
<constructor-argindex="1"value="我是通過構造器引數位置注入的"/>
</bean>
</beans>
上面建立UserModel例項程式碼相當於下面程式碼:
UserModeluserModel=newUserModel("路人甲Java","我是通過構造器引數型別注入的");
工具類IoUtils.java
packagecom.javacode2018.lesson001.demo5;
importorg.springframework.context.support.ClassPathXmlApplicationContext;
/**
*公眾號:路人甲Java,工作10年的前阿里P7分享Java、演算法、資料庫方面的技術乾貨!堅信用技術改變命運,讓家人過上更體面的生活!
*/
publicclassIocUtil{
publicstaticClassPathXmlApplicationContextcontext(StringbeanXml){
returnnewClassPathXmlApplicationContext(beanXml);
}
}
測試用例DiTest.java
packagecom.javacode2018.lesson001.demo5;
importorg.junit.Test;
importorg.springframework.context.support.ClassPathXmlApplicationContext;
/**
*公眾號:路人甲Java,工作10年的前阿里P7分享Java、演算法、資料庫方面的技術乾貨!堅信用技術改變命運,讓家人過上更體面的生活!
*/
publicclassDiTest{
/**
*通過構造器的引數位置注入
*/
@Test
publicvoiddiByConstructorParamIndex(){
StringbeanXml="classpath:/com/javacode2018/lesson001/demo5/diByConstructorParamIndex.xml";
ClassPathXmlApplicationContextcontext=IocUtils.context(beanXml);
System.out.println(context.getBean("diByConstructorParamIndex"));
}
}
效果
執行diByConstructorParamIndex輸出
UserModel{name='路人甲Java',age=0,desc='我是通過構造器引數位置注入的'}
優缺點
引數位置的注入對引數順序有很強的依賴性,若建構函式引數位置被人調整過,會導致注入出錯。
不過通常情況下,不建議去在程式碼中修改建構函式,如果需要新增引數的,可以新增一個建構函式來實現,這算是一種擴充套件,不會影響目前已有的功能。
根據構造器引數型別注入
用法
<beanid="diByConstructorParamType"class="com.javacode2018.lesson001.demo5.UserModel">
<constructor-argtype="引數型別"value="引數值"/>
<constructor-argtype="引數型別"value="引數值"/>
</bean>
constructor-arg使用者指定構造器的引數
type:建構函式引數的完整型別,如:java.lang.String,int,double
value:構造器引數的值,value只能用來給簡單的型別設定值
案例
diByConstructorParamType.xml
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="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-4.3.xsd">
<!--通過構造器引數的型別注入-->
<beanid="diByConstructorParamType"class="com.javacode2018.lesson001.demo5.UserModel">
<constructor-argtype="int"value="30"/>
<constructor-argtype="java.lang.String"value="路人甲Java"/>
<constructor-argtype="java.lang.String"value="我是通過構造器引數型別注入的"/>
</bean>
</beans>
上面建立UserModel例項程式碼相當於下面程式碼:
UserModeluserModel=newUserModel("路人甲Java",30,"我是通過構造器引數型別注入的");
新增測試用例
DiTest類中新增一個測試用例
/**
*通過構造器的引數型別注入
*/
@Test
publicvoiddiByConstructorParamType(){
StringbeanXml="classpath:/com/javacode2018/lesson001/demo5/diByConstructorParamType.xml";
ClassPathXmlApplicationContextcontext=IocUtils.context(beanXml);
System.out.println(context.getBean("diByConstructorParamType"));
}
效果
執行diByConstructorParamType輸出
UserModel{name='路人甲Java',age=30,desc='我是通過構造器引數型別注入的'}
優缺點
實際上按照引數位置或者按照引數的型別注入,都有一個問題,很難通過bean的配置檔案,知道這個引數是對應UserModel中的那個屬性的,程式碼的可讀性不好,比如我想知道這每個引數對應UserModel中的那個屬性,必須要去看UserModel的原始碼,下面要介紹按照引數名稱注入的方式比上面這2種更優秀一些。
根據構造器引數名稱注入
用法
<beanid="diByConstructorParamName"class="com.javacode2018.lesson001.demo5.UserModel">
<constructor-argname="引數型別"value="引數值"/>
<constructor-argname="引數型別"value="引數值"/>
</bean>
constructor-arg使用者指定構造器的引數
name:構造引數名稱
value:構造器引數的值,value只能用來給簡單的型別設定值
關於方法引數名稱的問題
java通過反射的方式可以獲取到方法的引數名稱,不過原始碼中的引數通過編譯之後會變成class物件,通常情況下原始碼變成class檔案之後,引數的真實名稱會丟失,引數的名稱會變成arg0,arg1,arg2這樣的,和實際引數名稱不一樣了,如果需要將原始碼中的引數名稱保留在編譯之後的class檔案中,編譯的時候需要用下面的命令:
javac-parametersjava原始碼
但是我們難以保證編譯程式碼的時候,操作人員一定會帶上-parameters引數,所以方法的引數可能在class檔案中會丟失,導致反射獲取到的引數名稱和實際引數名稱不符,這個我們需要先了解一下。
引數名稱可能不穩定的問題,spring提供瞭解決方案,通過ConstructorProperties註解來定義引數的名稱,將這個註解加在構造方法上面,如下:
@ConstructorProperties({"第一個引數名稱","第二個引數的名稱",..."第n個引數的名稱"})
public類名(Stringp1,Stringp2...,引數n){
}
案例
CarModel.java
packagecom.javacode2018.lesson001.demo5;
importjava.beans.ConstructorProperties;
publicclassCarModel{
privateStringname;
//描述資訊
privateStringdesc;
publicCarModel(){
}
@ConstructorProperties({"name","desc"})
publicCarModel(Stringp1,Stringp2){
this.name=p1;
this.desc=p2;
}
@Override
publicStringtoString(){
return"CarModel{"+
"name='"+name+'\''+
",desc='"+desc+'\''+
'}';
}
}
diByConstructorParamName.xml
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="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-4.3.xsd">
<!--通過構造器引數的名稱注入-->
<beanid="diByConstructorParamName"class="com.javacode2018.lesson001.demo5.CarModel">
<constructor-argname="desc"value="我是通過構造器引數型別注入的"/>
<constructor-argname="name"value="保時捷Macans"/>
</bean>
</beans>
上面建立CarModel例項程式碼相當於下面程式碼:
CarModelcarModel=newCarModel("保時捷Macans","我是通過構造器引數型別注入的");
新增測試用例
DiTest類中新增一個測試用例
/**
*通過構造器的引數名稱注入
*/
@Test
publicvoiddiByConstructorParamName(){
StringbeanXml="classpath:/com/javacode2018/lesson001/demo5/diByConstructorParamName.xml";
ClassPathXmlApplicationContextcontext=IocUtils.context(beanXml);
System.out.println(context.getBean("diByConstructorParamName"));
}
效果
執行diByConstructorParamName輸出
CarModel{name='保時捷Macans',desc='我是通過構造器引數型別注入的'}
setter注入
通常情況下,我們的類都是標準的javabean,javabean類的特點:
-
屬性都是private訪問級別的
-
屬性通常情況下通過一組setter(修改器)和getter(訪問器)方法來訪問
-
setter方法,以set開頭,後跟首字母大寫的屬性名,如:setUserName,簡單屬性一般只有一個方法引數,方法返回值通常為void;
-
getter方法,一般屬性以get開頭,對於boolean型別一般以is開頭,後跟首字母大寫的屬性名,如:getUserName,isOk;
spring對符合javabean特點類,提供了setter方式的注入,會呼叫對應屬性的setter方法將被依賴的物件注入進去。
用法
<beanid=""class="">
<propertyname="屬性名稱"value="屬性值"/>
...
<propertyname="屬性名稱"value="屬性值"/>
</bean>
property:用於對屬性的值進行配置,可以有多個
name:屬性的名稱
value:屬性的值
案例
MenuModel.java
packagecom.javacode2018.lesson001.demo5;
/**
*選單類
*/
publicclassMenuModel{
//選單名稱
privateStringlabel;
//同級別排序
privateIntegertheSort;
publicStringgetLabel(){
returnlabel;
}
publicvoidsetLabel(Stringlabel){
this.label=label;
}
publicIntegergetTheSort(){
returntheSort;
}
publicvoidsetTheSort(IntegertheSort){
this.theSort=theSort;
}
@Override
publicStringtoString(){
return"MenuModel{"+
"label='"+label+'\''+
",theSort="+theSort+
'}';
}
}
diBySetter.xml
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="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-4.3.xsd">
<beanid="diBySetter"class="com.javacode2018.lesson001.demo5.MenuModel">
<propertyname="label"value="spring系列"/>
</bean>
</beans>
新增測試用例
DiTest類中新增一個測試方法
/**
*通過setter方法注入
*/
@Test
publicvoiddiBySetter(){
StringbeanXml="classpath:/com/javacode2018/lesson001/demo5/diBySetter.xml";
ClassPathXmlApplicationContextcontext=IocUtils.context(beanXml);
System.out.println(context.getBean("diBySetter"));
}
效果
執行diBySetter輸出
MenuModel{label='spring系列',theSort=null}
優缺點
setter注入相對於建構函式注入要靈活一些,建構函式需要指定對應建構函式中所有引數的值,而setter注入的方式沒有這種限制,不需要對所有屬性都進行注入,可以按需進行注入。
上面介紹的都是注入普通型別的物件,都是通過value屬性來設定需要注入的物件的值的,value屬性的值是String型別的,spring容器內部自動會將value的值轉換為物件的實際型別。
若我們依賴的物件是容器中的其他bean物件的時候,需要用下面的方式進行注入。
注入容器中的bean
注入容器中的bean有兩種寫法:
-
ref屬性方式
-
內建bean的方式
ref屬性方式
將上面介紹的constructor-arg或者property元素的value屬性名稱替換為ref,ref屬性的值為容器中其他bean的名稱,如:
構造器方式,將value替換為ref:
<constructor-argref="需要注入的bean的名稱"/>
setter方式,將value替換為ref:
<propertyname="屬性名稱"ref="需要注入的bean的名稱"/>
內建bean的方式
構造器的方式:
<constructor-arg>
<beanclass=""/>
</constructor-arg>
setter方式:
<propertyname="屬性名稱">
<beanclass=""/>
</property>
案例
PersonModel.java
packagecom.javacode2018.lesson001.demo5;
publicclassPersonModel{
privateUserModeluserModel;
privateCarModelcarModel;
publicPersonModel(){
}
publicPersonModel(UserModeluserModel,CarModelcarModel){
this.userModel=userModel;
this.carModel=carModel;
}
publicUserModelgetUserModel(){
returnuserModel;
}
publicvoidsetUserModel(UserModeluserModel){
this.userModel=userModel;
}
publicCarModelgetCarModel(){
returncarModel;
}
publicvoidsetCarModel(CarModelcarModel){
this.carModel=carModel;
}
@Override
publicStringtoString(){
return"PersonModel{"+
"userModel="+userModel+
",carModel="+carModel+
'}';
}
}
PersonModel中有依賴於2個物件UserModel、CarModel,下面我們通過spring將UserModel和CarModel建立好,然後注入到PersonModel中,下面建立bean配置檔案
diBean.xml
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="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-4.3.xsd">
<beanid="user"class="com.javacode2018.lesson001.demo5.UserModel"></bean>
<!--通過構造器方式注入容器中的bean-->
<beanid="diBeanByConstructor"class="com.javacode2018.lesson001.demo5.PersonModel">
<!--通過ref引用容器中定義的其他bean,user對應上面定義的id="user"的bean-->
<constructor-argindex="0"ref="user"/>
<constructor-argindex="1">
<beanclass="com.javacode2018.lesson001.demo5.CarModel">
<constructor-argindex="0"value="賓利"/>
<constructor-argindex="1"value=""/>
</bean>
</constructor-arg>
</bean>
<!--通過setter方式注入容器中的bean-->
<beanid="diBeanBySetter"class="com.javacode2018.lesson001.demo5.PersonModel">
<!--通過ref引用容器中定義的其他bean,user對應上面定義的id="user"的bean-->
<propertyname="userModel"ref="user"/>
<propertyname="carModel">
<beanclass="com.javacode2018.lesson001.demo5.CarModel">
<constructor-argindex="0"value="保時捷"/>
<constructor-argindex="1"value=""/>
</bean>
</property>
</bean>
</beans>
新增測試用例
DiTest中新增一個測試方法
@Test
publicvoiddiBean(){
StringbeanXml="classpath:/com/javacode2018/lesson001/demo5/diBean.xml";
ClassPathXmlApplicationContextcontext=IocUtils.context(beanXml);
System.out.println(context.getBean("diBeanByConstructor"));
System.out.println(context.getBean("diBeanBySetter"));
}
效果
執行diBean用例,輸出:
PersonModel{userModel=UserModel{name='null',age=0,desc='null'},carModel=CarModel{name='賓利',desc=''}}
PersonModel{userModel=UserModel{name='null',age=0,desc='null'},carModel=CarModel{name='保時捷',desc=''}}
其他型別注入
注入java.util.List(list元素)
<list>
<value>Spring</value>
或
<refbean="bean名稱"/>
或
<list></list>
或
<bean></bean>
或
<array></array>
或
<map></map>
</list>
注入java.util.Set(set元素)
<set>
<value>Spring</value>
或
<refbean="bean名稱"/>
或
<list></list>
或
<bean></bean>
或
<array></array>
或
<map></map>
</set>
注入java.util.Map(map元素)
<map>
<entrykey="路人甲Java"value="30"key-ref="key引用的bean名稱"value-ref="value引用的bean名稱"/>
或
<entry>
<key>
value對應的值,可以為任意型別
</key>
<value>
value對應的值,可以為任意型別
</value>
</entry>
</map>
注入陣列(array元素)
<array>
陣列中的元素
</array>
注入java.util.Properties(props元素)
Properties類相當於鍵值都是String型別的Map物件,使用props進行注入,如下:
<props>
<propkey="key1">java高併發系列</prop>
<propkey="key2">mybatis系列</prop>
<propkey="key3">mysql系列</prop>
</props>
案例
對於上面這些型別來個綜合案例。
DiOtherTypeModel.java
packagecom.javacode2018.lesson001.demo5;
importjava.util.*;
publicclassDiOtherTypeModel{
privateList<String>list1;
privateSet<UserModel>set1;
privateMap<String,Integer>map1;
privateint[]array1;
privatePropertiesproperties1;
publicList<String>getList1(){
returnlist1;
}
publicvoidsetList1(List<String>list1){
this.list1=list1;
}
publicSet<UserModel>getSet1(){
returnset1;
}
publicvoidsetSet1(Set<UserModel>set1){
this.set1=set1;
}
publicMap<String,Integer>getMap1(){
returnmap1;
}
publicvoidsetMap1(Map<String,Integer>map1){
this.map1=map1;
}
publicint[]getArray1(){
returnarray1;
}
publicvoidsetArray1(int[]array1){
this.array1=array1;
}
publicPropertiesgetProperties1(){
returnproperties1;
}
publicvoidsetProperties1(Propertiesproperties1){
this.properties1=properties1;
}
@Override
publicStringtoString(){
return"DiOtherTypeModel{"+
"list1="+list1+
",set1="+set1+
",map1="+map1+
",array1="+Arrays.toString(array1)+
",properties1="+properties1+
'}';
}
}
上面這個類中包含了剛才我們介紹的各種型別,下面來進行注入。
diOtherType.xml
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="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-4.3.xsd">
<beanid="user1"class="com.javacode2018.lesson001.demo5.UserModel"/>
<beanid="user2"class="com.javacode2018.lesson001.demo5.UserModel"/>
<beanid="diOtherType"class="com.javacode2018.lesson001.demo5.DiOtherTypeModel">
<!--注入java.util.List物件-->
<propertyname="list1">
<list>
<value>Spring</value>
<value>SpringBoot</value>
</list>
</property>
<!--注入java.util.Set物件-->
<propertyname="set1">
<set>
<refbean="user1"/>
<refbean="user2"/>
<refbean="user1"/>
</set>
</property>
<!--注入java.util.Map物件-->
<propertyname="map1">
<map>
<entrykey="路人甲Java"value="30"/>
<entrykey="路人"value="28"/>
</map>
</property>
<!--注入陣列物件-->
<propertyname="array1">
<array>
<value>10</value>
<value>9</value>
<value>8</value>
</array>
</property>
<!--注入java.util.Properties物件-->
<propertyname="properties1">
<props>
<propkey="key1">java高併發系列</prop>
<propkey="key2">mybatis系列</prop>
<propkey="key3">mysql系列</prop>
</props>
</property>
</bean>
</beans>
新增測試用例
DiTest類中新增一個方法
/**
*其他各種型別注入
*/
@Test
publicvoiddiOtherType(){
StringbeanXml="classpath:/com/javacode2018/lesson001/demo5/diOtherType.xml";
ClassPathXmlApplicationContextcontext=IocUtils.context(beanXml);
System.out.println(context.getBean("diOtherType"));
}
效果
執行測試用例輸出:
DiOtherTypeModel{list1=[Spring,SpringBoot],set1=[UserModel{name='null',age=0,desc='null'},UserModel{name='null',age=0,desc='null'}],map1={路人甲Java=30,路人=28},array1=[10,9,8],properties1={key3=mysql系列,key2=mybatis系列,key1=java高併發系列}}
總結
-
本文主要講解了xml中bean的依賴注入,都是採用硬編碼的方式進行注入的,這種算是手動的方式
-
注入普通型別通過value屬性或者value元素設定注入的值;注入物件如果是容器的其他bean的時候,需要使用ref屬性或者ref元素或者內建bean元素的方式
-
還介紹了其他幾種型別List、Set、Map、陣列、Properties型別的注入,多看幾遍加深理解
-
後面我們將介紹spring為我們提供的更牛逼的自動注入
案例原始碼
連結:https://pan.baidu.com/s/1p6rcfKOeWQIVkuhVybzZmQ
提取碼:zr99
來源:https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648933967&idx=1&sn=3444809283b21222dd291a14dad0571b&chksm=88621e71bf159767f8e32e33488383d5841de7e13ca596d7c6572c8d97ba3ae143d3a3888463&token=1687118085&lang=zh_CN&scene=21#wechat_redirect