依賴注入的概念
如果要在一個類中,使用另一個類,傳統的方式是直接new:
class A{
//......
B b=new B();
//......
}
A類對象依賴於B類對象,如果沒有B類對象,A類對象就不能正常工作,稱為A依賴B。
上面的方式會增加A類與B類的耦合,不利於項目后期的升級(擴展)、維護。
在Spring中,B類的實例(被調用者),不再由A類(調用者)創建,而是由Spring容器創建,創建好以后,由Spring容器將B類實例注入A類實例中,稱為依賴注入(Dependency Injection,DI)。
原本是由A類主動創建B類對象(A類控制B類的創建),現在是Spring容器創建B類對象,注入A類對象中,A類被動接受Spring容器創建的B類實例,B類對象創建的控制權發生了反轉,所以又叫做控制反轉(Inversion of Control,IoC)。
控制反轉(IoC)是由依賴注入(DI)實現的,依賴注入又是由spring容器實現的,所以Spring容器又叫做Spring IoC容器。。
依賴注入是一種優秀的解耦方式,由Spring容器負責控制類(Bean)之間的關系,降低了類之間的耦合。
常用的方式有2種:
- 設值注入,也叫set方式注入
- 構造方法注入
設值注入
將依賴作為成員變量,通過主調類的setter方法注入依賴。
public class A { private B b; public void setB(B b) { this.b = b; } //...... }
xml配置:
<bean name="b" class="com.chy.bean.B" /> <bean name="a" class="com.chy.bean.A"> <property name="b" ref="b"/> </bean>
使用<property>注入依賴,name指定屬性名(成員變量名),ref指定要注入的bean(value指定要注入的常量值)。
如果要注入多個依賴,使用多個<property />即可。
構造方法注入
將依賴作為成員變量,通過主調類的構造方法注入依賴。
public class A { private B b; public A(B b) { this.b = b; } //...... }
xml配置:
<bean name="b" class="com.chy.bean.B" /> <bean name="a" class="com.chy.bean.A"> <constructor-arg name="b" ref="b" /> </bean>
一個<constructor-arg />注入一個參數,name指定構造方法中的形參名,ref指定要注入的bean(value指定要注入的常量值)。
形參可用 name="形參名" 指定,也可以使用 index="形參表下標" 來指定(第一個參數 => 下標0)。
如果有多個形參,使用多個<constructor-arg />即可,spring會調用對應的構造方法。
不管是設值注入,還是構造方法注入,都是將依賴作為成員變量,所以有時候也把注入依賴叫做注入屬性。
依賴可分為3種類型:
- 注入int、float、String之類的基本數據類型,使用value。
比如根據學號查學生信息,需要注入一個int型的學號。
spring會自動將值轉換為需要的類型,比如需要的String,value="chy"會以String的形式注入。value="1",如果需要的是int,就轉換為int注入,如果需要的是String,就轉換為String注入。
- 注入其他Bean的實例,使用ref。
- 注入數組、集合、Properties等復雜類型
注入復雜類型的依賴
將復雜類型的數據數據作為成員變量:
private Object[] arr; private List<Object> list; private Set<Object> set; private Map<String,Object> map; private Properties properties;
設值注入需提供對應的setter方法,構造方法注入需提供對應的構造方法。
設值注入:
<bean name="a" class="com.chy.bean.A"> <!-- 注入數組--> <property name="arr"> <array> <!-- 基本數據類型使用value,bean的實例使用ref,可嵌套其他復雜類型--> <value>chy</value> <ref bean="b" /> </array> </property> <!-- 注入List --> <property name="list"> <list> <value>chy</value> <ref bean="b" /> </list> </property> <!-- 注入Set --> <property name="set"> <set> <value>chy</value> <ref bean="b" /> </set> </property> <!-- 注入Map--> <property name="map"> <map> <!-- Map的key只能是String,基本數據類型用value,其他Bean的實例用value-ref --> <entry key="id" value="1" /> <entry key="user" value-ref="b" /> </map> </property> <!-- 注入Properties --> <property name="properties"> <props> <!-- 鍵、值都只能是String --> <prop key="username">chy</prop> <prop key="password">abcd</prop> </props> </property> </bean>
<property name=""> name指定屬性名(成員變量名)。
數組、List、Set的配置方式是差不多的。
構造方法注入:
<constructor-arg name="arr">
<array>
<value>chy</value>
<ref bean="b" />
</array>
</constructor-arg>
配置方式和設值注入差不多,只不過將 property 換為 constructor-arg ,name是指定形參名。
用的最多的是設值注入,因為很多時候都要修改成員變量的值,setter方法一般都要寫,直接用setter方法設值注入,沒必要再寫帶參的構造方法。
使用自動裝配注入其它Bean的實例
原先注入其它bean的實例,需要使用<property />或<constructor />注入。
使用自動裝配后,不需要使用<property />、<constructor />,spring會自動在容器中找到滿足要求的其它bean,注入進來。
<bean name="a" class="com.chy.bean.A" autowire="byName" />
可選的值:
- no 默認值,不使用自動裝配,如果要注入其它bean的實例,需要使用<property />或<constructor />。
- byName 根據name來自動裝配
- byType 根據type來自動裝配
- constructor 根據構造函數形參類型進行byType方式的自動裝配
- default 使用全局默認的自動裝配方式。
byName和byType都能在設值注入中使用,constructor只能在構造方法注入中使用。
byName
public class A { private B b; public void setB(B b) { this.b = b; } }
<bean name="b" class="com.chy.bean.B" /> <bean name="a" class="com.chy.bean.A" autowire="byName" />
A依賴B,A中有對應的setter方法。
byName,name就是setter方法的方法名,去掉set,后面部分使用camel寫法,比如setB() => b,
spring會自動到容器中找到name="b"的bean,注入。
name="b"的bean只能有一個,不然spring不知道要注入哪個。
byType
和byName差不多,也是要配合setter方法使用。不同的是:
byType,type是形參表的參數類型。比如setB(B b),參數類型是B,Spring自動找到class=“B”的bean,注入。
class=“B”的bean只能有一個,不然spring不知道要注入哪個。
constructor
public class A { private B b; public A(B b) { this.b = b; } }
顧名思議,需要和構造方法注入搭配使用。
找到帶參的構造器,根據參數類型,按照byType的方式注入依賴。
default
使用全局默認的自動裝配方式。
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd" default-autowire="byName"> <bean name="b" class="com.chy.bean.B" /> <bean name="a" class="com.chy.bean.A" autowire="default" /> </beans>
需要在根元素<beans>中設置全局默認的自動裝配方式,所有default方式的自動裝配都是使用 default-autowire 設置的方式進行裝配。
自動裝配的貪婪原則
自動裝配會盡量多地注入依賴。
比如constructor ,有2個構造方法:(B b)、(B b, C c),如果容器中有B、C的實例,則優先調用(B b, C c),會盡量多地注入依賴。
自動裝配的優點:一個屬性就搞定,不必寫大量的<property />或<constructor />。
缺點:自動裝配只能注入其它bean的實例,使用時有限制條件。(可以和注入基本類型、復雜類型的方式一起使用。實際上,注入基本類型、bean的實例、復雜類型都可以搭配使用。)
使用SpEL注入依賴
SpEL,即Spring Expression Language,spring表達式語言。
使用SpEL可以注入基本類型,可以注入其它Bean,可以訪問其它Bean的成員變量、調用其它Bean中的方法。
使用示例:
<bean name="score" class="com.chy.bean.Score"> <property name="chinese" value="#{90}" /> <property name="math" value="#{100}" /> <property name="english" value="#{95}" /> </bean> <bean name="student" class="com.chy.bean.Student"> <property name="no" value="#{1}" /> <property name="name" value="#{'chy'}" /> <property name="score" value="#{score}" /> </bean>
SpEL放在#{ }中,數值型直接寫,字符、字符串要加單引號,可以直接引用其它Bean。
SpEL使用的是設值注入,所以需要提供setter方法,只能用<property />注入值,不能使用構造方法注入。
不管值是什么類型,都只能用value,不能用ref。
可以直接訪問其它Bean的成員變量,比如 #{score.math} ,實質是調用對應的getter方法,所以需要提供getter方法。
可以調用其它Bean的方法,比如 #{score.getMath()},如果該方法返回void,作為null處理。