手動注入的不足
上篇文章中介紹了依賴注入中的手動注入,所謂手動注入是指在xml中採用硬編碼的方式來配置注入的物件,比如透過構造器注入或者set方法注入,這些注入的方式都存在不足,比如下面程式碼:
public class A{
private B b;
private C c;
private D d;
private E e;
....
private N n;
//上面每個private屬性的get和set方法}12345678910
使用spring容器來管理,xml配置如下:
<bean class="b" class="B"/><bean class="c" class="C"/><bean class="d" class="D"/><bean class="e" class="E"/><bean class="a" class="A">
<property name="b" ref="b"/>
<property name="c" ref="c"/>
<property name="d" ref="d"/>
<property name="e" ref="e"/>
...</bean>1234567891011
上面的注入存在的問題:
為了解決上面這些問題,spring為我們提供了更強大的功能:自動注入
在介紹自動注入之前需要先介紹一些基礎知識。
Class.isAssignableFrom方法
用法
isAssignableFrom是Class類中的一個方法,看一下這個方法的定義:
public native boolean isAssignableFrom(Class<?> cls)1
用法如下:
c1.isAssignableFrom(c2)1
用來判斷c2和c1是否相等,或者c2是否是c1的子類。
案例
@Testpublic void isAssignableFrom(){
System.out.println(Object.class.isAssignableFrom(Integer.class)); //true
System.out.println(Object.class.isAssignableFrom(int.class)); //false
System.out.println(Object.class.isAssignableFrom(List.class)); //true
System.out.println(Collection.class.isAssignableFrom(List.class)); //true
System.out.println(List.class.isAssignableFrom(Collection.class)); //false}12345678
自動注入
自動注入是採用約定大約配置的方式來實現的,程式和spring容器之間約定好,遵守某一種都認同的規則,來實現自動注入。
xml中可以在bean元素中透過autowire屬性來設定自動注入的方式:
<bean id="" class="" autowire="byType|byName|constructor|default" />1
-
byteName:按照名稱進行注入
-
byType:按型別進行注入
-
constructor:按照構造方法進行注入
-
default:預設注入方式
下面我們詳解每種注入方式的用法。
按照名稱進行注入(byName)
用法
autowire設定為byName
<bean id="" class="X類" autowire="byName"/>1
spring容器會按照set屬性的名稱去容器中查詢同名的bean物件,然後將查詢到的物件透過set方法注入到對應的bean中,未找到對應名稱的bean物件則set方法不進行注入
需要注入的set屬性的名稱和被注入的bean的名稱必須一致。
來看看案例吧。
案例
DiByName.java
package com.javacode2018.lesson001.demo6;/**
* 按照名稱自動注入
*/public class DiAutowireByName {
public static class Service1 { //@1
private String desc;
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "Service1{" +
"desc='" + desc + '\'' +
'}';
}
}
public static class Service2 { //@1
private String desc;
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "Service2{" +
"desc='" + desc + '\'' +
'}';
}
}
private Service1 service1;//@3
private Service2 service2;//@4
public Service1 getService1() {
return service1;
}
public void setService1(Service1 service1) {
System.out.println("setService1->" + service1);
this.service1 = service1;
}
public Service2 getService2() {
return service2;
}
public void setService2(Service2 service2) {
System.out.println("setService2->" + service2);
this.service2 = service2;
}
@Override
public String toString() {
return "DiAutowireByName{" +
"service1=" + service1 +
", service2=" + service2 +
'}';
}}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
這個類中有2個屬性,名稱為:
這兩個屬性都有對應的set方法。
下面我們在bean xml中定義2個和這2個屬性同名的bean,然後使用按照名稱進行自動注入。
diAutowireByName.xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="
xmlns:xsi="
xsi:schemaLocation="
/spring-beans-4.3.xsd">
<bean id="service1" class="com.javacode2018.lesson001.demo6.DiAutowireByName$Service1">
<property name="desc" value="service1"/>
</bean>
<bean id="service2" class="com.javacode2018.lesson001.demo6.DiAutowireByName$Service2">
<property name="desc" value="service2"/>
</bean>
<bean id="service2-1" class="com.javacode2018.lesson001.demo6.DiAutowireByName$Service2">
<property name="desc" value="service2-1"/>
</bean>
<!-- autowire:byName 配置按照name進行自動注入 -->
<bean id="diAutowireByName1" class="com.javacode2018.lesson001.demo6.DiAutowireByName" autowire="byName"/>
<!-- 當配置了自動注入,還可以使用手動的方式自動注入進行覆蓋,手動的優先順序更高一些 -->
<bean id="diAutowireByName2" class="com.javacode2018.lesson001.demo6.DiAutowireByName" autowire="byName">
<property name="service2" ref="service2-1"/>
</bean></beans>12345678910111213141516171819202122232425
上面註釋認真看一下。
@1:定義了一個名稱為service1的bean
@2:定義了一個名稱為service2的bean
@3:定義diAutowireByName需要將autowire的值置為byName,表示按名稱進行自動注入。
spring容器建立diAutowireByName對應的bean時,會遍歷DiAutowireByName類中的所有set方法,然後得到set對應的屬性名稱列表:{“service1”,“service2”},然後遍歷這屬性列表,在容器中查詢和屬性同名的bean物件,然後呼叫屬性對應的set方法,將bean物件注入進去
測試用例
package com.javacode2018.lesson001.demo6;import com.javacode2018.lesson001.demo5.IocUtils;import org.junit.Test;import org.springframework.context.support.ClassPathXmlApplicationContext;import java.util.ArrayList;import java.util.Collection;import java.util.List;public class DiAutowireTest {
/**
* 按照名稱進行注入
*/
@Test
public void diAutowireByName() {
String beanXml = "classpath:/com/javacode2018/lesson001/demo6/diAutowireByName.xml";
ClassPathXmlApplicationContext context = IocUtils.context(beanXml);
System.out.println(context.getBean("diAutowireByName"));
}}12345678910111213141516171819202122
效果
執行diAutowireByName輸出:
setService1->Service1{desc='service1'}setService2->Service2{desc='service2'}setService2->Service2{desc='service2-1'}setService1->Service1{desc='service1'}DiAutowireByName{service1=Service1{desc='service1'}, service2=Service2{desc='service2'}}DiAutowireByName{service1=Service1{desc='service1'}, service2=Service2{desc='service2-1'}}123456
優缺點
按名稱進行注入的時候,要求名稱和set屬性的名稱必須同名,相對於硬編碼的方式注入,確實節省了不少程式碼。
按照型別進行自動注入
用法
autowire設定為byType
<bean id="" class="X類" autowire="byType"/>1
spring容器會遍歷x類中所有的set方法,會在容器中查詢和set引數型別相同的bean物件,將其透過set方法進行注入,未找到對應型別的bean物件則set方法不進行注入。
需要注入的set屬性的型別和被注入的bean的型別需要滿足isAssignableFrom關係。
按照型別自動裝配的時候,如果按照型別找到了多個符合條件的bean,系統會報錯。
set方法的引數如果是下面的型別或者下面型別的陣列的時候,這個set方法會被跳過注入:
Object,Boolean,boolean,Byte,byte,Character,char,Double,double,Float,float,Integer,int,Long,Short,shot,Enum,CharSequence,Number,Date,java.time.temporal.Temporal,java.net.URI,java.net.URI,java.util.Locale,java.lang.Class
來看看案例吧。
案例
DiByType.java
package com.javacode2018.lesson001.demo6;/**
* 按照型別自動注入
*/public class DiAutowireByType {
public static class Service1 {
private String desc;
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "Service1{" +
"desc='" + desc + '\'' +
'}';
}
}
public static class Service2 {
private String desc;
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "Service2{" +
"desc='" + desc + '\'' +
'}';
}
}
private Service1 service1;
private Service2 service2;
public Service1 getService1() {
return service1;
}
public void setService1(Service1 service1) {
System.out.println("setService1->" + service1); //@1
this.service1 = service1;
}
public Service2 getService2() {
return service2;
}
public void setService2(Service2 service2) {
System.out.println("setService2->" + service2); //@2
this.service2 = service2;
}
@Override
public String toString() {
return "DiByType{" +
"service1=" + service1 +
", service2=" + service2 +
'}';
}}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
DiAutowireByType類中有2個set方法分別來注入Service1和Service2,兩個set方法中都輸出了一行文字,一會執行的時候可以透過這個輸出可以看出set方法是否被呼叫了。
diAutowireByName.xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="
xmlns:xsi="
xsi:schemaLocation="
/spring-beans-4.3.xsd">
<bean id="service1" class="com.javacode2018.lesson001.demo6.DiAutowireByName$Service1">
<property name="desc" value="service1"/>
</bean>
<bean id="service2" class="com.javacode2018.lesson001.demo6.DiAutowireByName$Service2">
<property name="desc" value="service2"/>
</bean>
<bean id="service2-1" class="com.javacode2018.lesson001.demo6.DiAutowireByName$Service2">
<property name="desc" value="service2-1"/>
</bean>
<!-- autowire:byName 配置按照name進行自動注入 -->
<bean id="diAutowireByName1" class="com.javacode2018.lesson001.demo6.DiAutowireByName" autowire="byName"/>
<!-- 當配置了自動注入,還可以使用手動的方式自動注入進行覆蓋,手動的優先順序更高一些 -->
<bean id="diAutowireByName2" class="com.javacode2018.lesson001.demo6.DiAutowireByName" autowire="byName">
<property name="service2" ref="service2-1"/>
</bean></beans>12345678910111213141516171819202122232425
上面註釋認真看一下。
@1:定義了一個名稱為service1的bean
@2:定義了一個名稱為service2的bean
@3:定義diAutowireByName需要將autowire的值置為byName,表示按名稱進行自動注入。
spring容器建立diAutowireByName對應的bean時,會遍歷DiAutowireByName類中的所有set方法,然後得到set對應的屬性名稱列表:{“service1”,“service2”},然後遍歷這屬性列表,在容器中查詢和屬性同名的bean物件,然後呼叫屬性對應的set方法,將bean物件注入進去
測試用例
DiAutowireTest類中新增一個方法
/**
* 按照set方法引數型別進行注入
*/@Testpublic void diAutowireByType() {
String beanXml = "classpath:/com/javacode2018/lesson001/demo6/diAutowireByType.xml";
ClassPathXmlApplicationContext context = IocUtils.context(beanXml);
System.out.println(context.getBean("diAutowireByType1"));}123456789
效果
執行diAutowireByType輸出:
setService1->Service1{desc='service1'}setService2->Service2{desc='service2'}DiByType{service1=Service1{desc='service1'}, service2=Service2{desc='service2'}}123
優缺點
相對於手動注入,節省了不少程式碼,新增或者刪除屬性,只需要增減對應的set方法就可以了,更容易擴充套件了。
注入型別匹配的所有bean(重點)
按照型別注入還有2中比較牛逼的用法:
一個容器中滿足某種型別的bean可以有很多個,將容器中某種型別中的所有bean,透過set方法注入給一個java.util.List<需要注入的Bean的型別或者其父型別或者其介面>物件
將容器中某種型別中的所有bean,透過set方法注入給一個java.util.Map<String,需要注入的Bean的型別或者其父型別或者其介面>物件
來看個案例就懂了。
DiAutowireByTypeExtend.java
package com.javacode2018.lesson001.demo6;import java.util.List;import java.util.Map;/**
* 滿足條件的bean注入到集合中(重點)
*/public class DiAutowireByTypeExtend {
//定義了一個介面
public interface IService1 {
}
public static class BaseServie {
private String desc;
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "BaseServie{" +
"desc='" + desc + '\'' +
'}';
}
}
//Service1實現了IService1介面
public static class Service1 extends BaseServie implements IService1 {
}
//Service1實現了IService1介面
public static class Service2 extends BaseServie implements IService1 {
}
private List<IService1> serviceList;//@1
private List<BaseServie> baseServieList;//@2
private Map<String, IService1> service1Map;//@3
private Map<String, BaseServie> baseServieMap;//@4
public List<IService1> getServiceList() {
return serviceList;
}
public void setServiceList(List<IService1> serviceList) {//@5
this.serviceList = serviceList;
}
public List<BaseServie> getBaseServieList() {
return baseServieList;
}
public void setBaseServieList(List<BaseServie> baseServieList) {//@6
this.baseServieList = baseServieList;
}
public Map<String, IService1> getService1Map() {
return service1Map;
}
public void setService1Map(Map<String, IService1> service1Map) {//@7
this.service1Map = service1Map;
}
public Map<String, BaseServie> getBaseServieMap() {
return baseServieMap;
}
public void setBaseServieMap(Map<String, BaseServie> baseServieMap) {//@8
this.baseServieMap = baseServieMap;
}
@Override
public String toString() { //9
return "DiAutowireByTypeExtend{" +
"serviceList=" + serviceList +
", baseServieList=" + baseServieList +
", service1Map=" + service1Map +
", baseServieMap=" + baseServieMap +
'}';
}}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
@1,@2,@3,@4:定義了4個屬性,都是泛型型別的,都有對應的set方法。
@5:引數型別是List,這個集合集合中元素的型別是BaseServie,spring會找到容器中所有滿足BaseServie.isAssignableFrom(bean的型別)的bean列表,將其透過@5的set方法進行注入。
@6:同@5的程式碼
@7:這個引數型別是一個map了,map的key是string型別,value是IService1型別,spring容器會將所有滿足IService1型別的bean找到,按照name->bean物件這種方式丟到一個map中,然後呼叫@7的set方法進行注入,最後注入的這個map就是bean的名稱和bean物件進行對映的一個map物件。
@8:同@7的程式碼
@9:重寫了toString方法,輸出的時候好看一些
測試用例
DiAutowireTest新增一個方法:
/**
* 按照型別注入集合
*/@Testpublic void diAutowireByTypeExtend() {
String beanXml = "classpath:/com/javacode2018/lesson001/demo6/diAutowireByTypeExtend.xml";
ClassPathXmlApplicationContext context = IocUtils.context(beanXml);
//從容器中獲取DiAutowireByTypeExtend
DiAutowireByTypeExtend diAutowireByTypeExtend = context.getBean(DiAutowireByTypeExtend.class);
//輸出diAutowireByTypeExtend中的屬性看一下
System.out.println("serviceList:" + diAutowireByTypeExtend.getServiceList());
System.out.println("baseServieList:" + diAutowireByTypeExtend.getBaseServieList());
System.out.println("service1Map:" + diAutowireByTypeExtend.getService1Map());
System.out.println("baseServieMap:" + diAutowireByTypeExtend.getBaseServieMap());}123456789101112131415
效果
執行diAutowireByTypeExtend輸出:
serviceList:[BaseServie{desc='service1-1'}, BaseServie{desc='service1-2'}, BaseServie{desc='service2-1'}]baseServieList:[BaseServie{desc='service1-1'}, BaseServie{desc='service1-2'}, BaseServie{desc='service2-1'}]service1Map:{service1-1=BaseServie{desc='service1-1'}, service1-2=BaseServie{desc='service1-2'}, service2-1=BaseServie{desc='service2-1'}}baseServieMap:{service1-1=BaseServie{desc='service1-1'}, service1-2=BaseServie{desc='service1-2'}, service2-1=BaseServie{desc='service2-1'}}12345
下面我們來介紹另外一種自動注入方式。
按照建構函式進行自動注入
用法
autowire設定為constructor
<bean id="" class="X類" autowire="constructor"/>1
spring會找到x類中所有的構造方法(一個類可能有多個構造方法),然後將這些構造方法進行排序(先按修飾符進行排序,public的在前面,其他的在後面,如果修飾符一樣的,會按照建構函式引數數量倒敘,也就是採用貪婪的模式進行匹配,spring容器會盡量多注入一些需要的物件)得到一個建構函式列表,會輪詢這個構造器列表,判斷當前構造器所有引數是否在容器中都可以找到匹配的bean物件,如果可以找到就使用這個構造器進行注入,如果不能找到,那麼就會跳過這個構造器,繼續採用同樣的方式匹配下一個構造器,直到找到一個合適的為止。
來看看案例吧。
案例
DiAutowireByConstructor.java
package com.javacode2018.lesson001.demo6;/**
* 建構函式的方式進行自動注入
*/public class DiAutowireByConstructor {
public static class BaseServie {
private String desc;
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "BaseServie{" +
"desc='" + desc + '\'' +
'}';
}
}
//Service1實現了IService1介面
public static class Service1 extends BaseServie {
}
//Service1實現了IService1介面
public static class Service2 extends BaseServie {
}
private Service1 service1;
private Service2 service2;
public DiAutowireByConstructor() { //@0
}
public DiAutowireByConstructor(Service1 service1) { //@1
System.out.println("DiAutowireByConstructor(Service1 service1)");
this.service1 = service1;
}
public DiAutowireByConstructor(Service1 service1, Service2 service2) { //@2
System.out.println("DiAutowireByConstructor(Service1 service1, Service2 service2)");
this.service1 = service1;
this.service2 = service2;
}
public Service1 getService1() {
return service1;
}
public void setService1(Service1 service1) {
this.service1 = service1;
}
public Service2 getService2() {
return service2;
}
public void setService2(Service2 service2) {
this.service2 = service2;
}
@Override
public String toString() {
return "DiAutowireByConstructor{" +
"service1=" + service1 +
", service2=" + service2 +
'}';
}}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
@1:1個引數的建構函式
@2:2個引數的建構函式
/guiyang/
2個有參建構函式第一行都列印了一段文字,一會在輸出中可以看到程式碼是呼叫了那個建構函式建立物件。
diAutowireByConstructor.xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="
xmlns:xsi="
xsi:schemaLocation="
/spring-beans-4.3.xsd">
<bean class="com.javacode2018.lesson001.demo6.DiAutowireByConstructor$Service1">
<property name="desc" value="service1"/>
</bean>
<bean id="diAutowireByConstructor" class="com.javacode2018.lesson001.demo6.DiAutowireByConstructor"
autowire="constructor"/></beans>1234567891011121314
測試用例
DiAutowireTest新增一個方法
/**
* 建構函式的方式進行自動注入
*/@Testpublic void diAutowireByConstructor() {
String beanXml = "classpath:/com/javacode2018/lesson001/demo6/diAutowireByConstructor.xml";
ClassPathXmlApplicationContext context = IocUtils.context(beanXml);
System.out.println(context.getBean("diAutowireByConstructor"));}123456789
效果
執行diAutowireByConstructor輸出:
DiAutowireByConstructor(Service1 service1)DiAutowireByConstructor{service1=BaseServie{desc='service1'}, service2=null}12
從輸出中可以看到呼叫的是DiAutowireByConstructor類中的第一個建構函式注入了service1 bean。
建構函式匹配採用貪婪匹配,多個建構函式結合容器找到一個合適的建構函式,最匹配的就是第一個有參建構函式,而第二個有參建構函式的第二個引數在spring容器中找不到匹配的bean物件,所以被跳過了。
我們在diAutowireByConstructor.xml加入Service2的配置:
/epilepsy/
<bean class="com.javacode2018.lesson001.demo6.DiAutowireByConstructor$Service2">
<property name="desc" value="service2"/></bean>123
再來執行一下diAutowireByConstructor輸出:
DiAutowireByConstructor(Service1 service1, Service2 service2)DiAutowireByConstructor{service1=BaseServie{desc='service1'}, service2=BaseServie{desc='service2'}}12
此時可以看到第二個有參建構函式被呼叫了,滿足了貪婪方式的注入原則,最大限度的注入所有依賴的物件。
autowire=default
用法
bean xml的根元素為beans,注意根元素有個default-autowire屬性,這個屬性可選值有(no|byName|byType|constructor|default),這個屬性可以批次設定當前檔案中所有bean的自動注入的方式,bean元素中如果省略了autowire屬性,那麼會取default-autowire的值作為其autowire的值,而每個bean元素還可以單獨設定自己的autowire覆蓋default-autowire的配置,如下:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="
xmlns:xsi="
xsi:schemaLocation="
/spring-beans-4.3.xsd" default-autowire="byName"></beans>12345678
案例
diAutowireByDefault.xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="
xmlns:xsi="
xsi:schemaLocation="
/spring-beans-4.3.xsd" default-autowire="byName"> //@1
<bean id="service1" class="com.javacode2018.lesson001.demo6.DiAutowireByName$Service1">
<property name="desc" value="service1"/>
</bean>
<bean id="service2" class="com.javacode2018.lesson001.demo6.DiAutowireByName$Service2">
<property name="desc" value="service2"/>
</bean>
<bean id="service2-1" class="com.javacode2018.lesson001.demo6.DiAutowireByName$Service2">
<property name="desc" value="service2-1"/>
</bean>
<!-- autowire:default,會採用beans中的default-autowire指定的配置 -->
<bean id="diAutowireByDefault1" class="com.javacode2018.lesson001.demo6.DiAutowireByName" autowire="default"/> //@2
<!-- autowire:default,會採用beans中的default-autowire指定的配置,還可以使用手動的方式自動注入進行覆蓋,手動的優先順序更高一些 -->
<bean id="diAutowireByDefault2" class="com.javacode2018.lesson001.demo6.DiAutowireByName" autowire="default"> //@3
<property name="service2" ref="service2-1"/>
</bean></beans>1234567891011121314151617181920212223242526
注意上面的@1配置的default-autowire=“byName”,表示全域性預設的自動注入方式是:按名稱注入
@2和@3的autowire=default,那麼注入方式會取default-autowire的值。
測試用例
/primary/
DiAutowireTest中新增一個方法
/**
* autowire=default
*/@Testpublic void diAutowireByDefault() {
String beanXml = "classpath:/com/javacode2018/lesson001/demo6/diAutowireByDefault.xml";
ClassPathXmlApplicationContext context = IocUtils.context(beanXml);
System.out.println(context.getBean("diAutowireByDefault1"));
System.out.println(context.getBean("diAutowireByDefault2"));}12345678910
效果
/cure/
執行diAutowireByDefault輸出
setService1->Service1{desc='service1'}setService2->Service2{desc='service2'}setService2->Service2{desc='service2-1'}setService1->Service1{desc='service1'}DiAutowireByName{service1=Service1{desc='service1'}, service2=Service2{desc='service2'}}DiAutowireByName{service1=Service1{desc='service1'}, service2=Service2{desc='service2-1'}}123456
總結
/yunnan/
xml中手動注入存在的不足之處,可以透過自動注入的方式來解決,本文介紹了3中自動注入:透過名稱自動注入、透過型別自動注入、透過構造器自動注入
按型別注入中有個比較重要的是注入匹配型別所有的bean,可以將某種型別所有的bean注入給一個List物件,可以將某種型別的所有bean按照
bean名稱->bean物件
的對映方式注入給一個Map物件,這種用法比較重要,用途比較大,要掌握
spring中還有其他自動注入的方式,用起來會更爽,後面的文章中我們會詳細介紹。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/30239065/viewspace-2731171/,如需轉載,請註明出處,否則將追究法律責任。