javascript
Spring-依赖注入
- 概述
- 屬性注入
- 屬性注入實例
- 代碼演示
- JavaBean關于屬性命名的特殊規范
- 屬性注入實例
- 構造函數注入
- 按類型匹配入參
- 按索引匹配入參
- 聯合使用類型和索引匹配入參
- 通過自身反射類型匹配入參
- 循環依賴問題
- 工廠方法注入
- 選擇注入方式的考量
概述
Spring支持兩種依賴注入的方式
- 屬性注入
- 構造函數注入
此外Spring還支持工廠方法注入。 這篇博文我們將了解到不同注入方式的具體配置方法。
屬性注入
屬性注入指的是通過setXxx()方法注入Bean的屬性值或者依賴對象。
由于屬性注入方式具有可選擇性和靈活性高的有點,因此屬性注入是實際應用中最常用的注入方式。
屬性注入實例
屬性注入的要求
Spring先調用Bean的默認構造函數實例化Bean對象,然后通過反射調用Setter方法注入屬性值。
代碼演示
POJO對象
package com.xgj.ioc.inject.set;public class Plane {private String brand;private String color;private int speed;/*** 在沒有其他顯示構造函數的情況下,默認構造函數可省略,有則必須聲明*/public Plane(){}public Plane(String brand,String color,int speed){this.brand = brand;this.color = color;this.speed = speed;}public void setBrand(String brand) {this.brand = brand;}public void setColor(String color) {this.color = color;}public void setSpeed(int speed) {this.speed = speed;}public void introduce() {System.out.println("Plane brand:" + brand + ",color:" + color+ ",speed," + speed);} }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="plane" class="com.xgj.ioc.inject.set.Plane"><property name="brand"><value>A380</value></property><property name="color"><value>red</value></property><property name="speed"><value>700</value></property></bean> </beans>測試類:
package com.xgj.ioc.inject.set;import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;public class SetTest {public static void main(String[] args) {// 加載配置文件,實例化beanApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:com/xgj/ioc/inject/set/plane.xml");// 從容器中獲取beanPlane plane = ctx.getBean("plane",Plane.class);// 調用方法plane.introduce();}}結果:
解讀:
上面的代碼配置了一個Bean,并為該Bean的3個屬性提供了屬性值, 具體來說Bean的每一個屬性對應一個property標簽,name為屬性的名稱(上述配置較為繁瑣,后續介紹p的用法),在Bean實現類中擁有與其對應的Setter方法: 比如 brand對應setBrand()。
有一點需要注意: spring只會檢查Bean中是否有對應的Setter方法,至于Bean中是否有對應的屬性成員變更則不做要求。
舉個例子:
配置文件中<property name="brand">的屬性配置項僅要求Plane中擁有setBrand()方法,但Plane類中不一定要擁有brand成員變量。
比如
private String color; private int speed; // 僅擁有setBrand方法,但是類中沒有brand成員變量 public void setBrand(String brand) {......}但一般情況下,還是按照約定俗成的方式在Bean中提供同名的屬性變量
注意:
默認構造函數是不帶參數的構造函數。 Java語言規定,如果類中沒有定義任何構造函數,JVM會自動為其生成一個默認的構造函數;反之,如果類中顯式的定義了構造函數,JVM則不會為其生成默認的構造函數。如果類中顯式的聲明了其他構造函數,如果未提供一個默認的構造函數,則屬性注入時,會拋出異常。
舉例:
我們在Plane中增加一個顯式構造函數,去掉默認的構造函數
運行測試類,
Failed to instantiate [com.xgj.ioc.inject.set.Plane]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.xgj.ioc.inject.set.Plane.<init>()JavaBean關于屬性命名的特殊規范
Spring配置文件中property元素所指定的屬性名和Bean實現類的Setter方法滿足Sun JavaBean的屬性命名規范: xxx的屬性對應setXxx()方法.
一般情況下,Java的屬性變量名都以小寫字母開頭,比如brand,speed等,但也有些特殊情況的存在。考慮到一些特定意義的大寫英文縮略字母(比如USA、XML等),Javabean也允許以大寫字母開頭的屬性變量名,不過必須滿足 變量的前兩個字母要么全部大寫,要么全部小寫。
比如brand,IDCode、IC等是合法的,而iC、iDCard等是非法的。
比如我們在Plane類中添加屬性和setter方法
// 非法的屬性變量名,但是Java并不會報錯,因為它將iDCard看做普通的變量private String iDCard;// 改setter方法對應IDCard屬性,而非iDCard屬性public void setIDCard(String iDCard) {this.iDCard = iDCard;}因為xxx的屬性對應setXxx()方法. 所以是setIDCard().
bean配置文件
STS中校驗都未通過,更加無法運行了。
總結:
以大寫字母開頭的變量總是顯得比較另類,為了規避這種詭異的錯誤,用戶可遵照以下編程的經驗:比如QQ、MSN、ID等正常情況下以大寫字母出現的專業術語,在Java中一律調整為小寫形式,如qq、msn、id等,以保證命名的統一性(變量名稱都小寫字母開頭),減少出錯概率。
構造函數注入
構造函數注入是除了屬性注入之外另外一種常用的注入方式,構造函數注入保證一些必要的屬性在Bean實例化的時候得到設置,確保Bean在實例化之后就可以使用
按類型匹配入參
舉個例子,假設任何使用Tank對象都必須提供brand和weight,若使用屬性注入 方式,這只能人為在配置時候提供保證而無法再語法級提供保證,這時候構造函數注入就可以很好地滿足這一個需求。
使用構造函數的前提是Bean必須提供帶參的構造函數。
代碼演示:
Pojo Bean實現類
package com.xgj.ioc.inject.construct;public class Tank {private String brand;private double weight;public Tank(String brand, double weight) {super();this.brand = brand;this.weight = weight;}public void introduce() {System.out.println("Tank information: brand:" + brand + ",weight:"+ weight + "KG");}}配置文件
<?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="tank" class="com.xgj.ioc.inject.construct.Tank"><constructor-arg type="java.lang.String"><value>T72</value></constructor-arg><constructor-arg type="double"><value>15000.00</value></constructor-arg></bean> </beans>測試類:
package com.xgj.ioc.inject.construct;import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;public class ConstructInjectTest {public static void main(String[] args) {ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:com/xgj/ioc/inject/construct/tank.xml");Tank tank = ctx.getBean("tank", Tank.class);tank.introduce();}}運行結果:
解讀:
在constructor-arg的元素中有個type屬性,它為Spring提供了判斷配置項和構造函數入參對應關系的信息。
一個疑問
- 配置文件中constructor-arg聲明順序難道不能用于確定構造函數入參的順序嗎?—在只有一個構造函數的情況下當然是可以的,如果類中定義了多個具有相同入參的構造函數,這種順序標識就失效了。
另外,Spring的配置文件采用和元素標簽順序無關的策略,一定程度上保證了配置信息的確定性,避免一些似是而非的問題。
<constructor-arg type="java.lang.String"><value>T72</value></constructor-arg><constructor-arg type="double"><value>15000.00</value></constructor-arg>這兩個參數的位置并不會影響對最終的配置效果產生影響。
按索引匹配入參
眾所周知,Java語言通過入參的類型和順序區分不同的重載方法。
如果Tank類中有兩個相同類型的入參,僅僅通過type就無法確定的對應關系了。這是需要通過入參索引的方式進行確定。
POJO對象
package com.xgj.ioc.inject.construct.index;public class Tank {private String brand;private double weight;private double speed;/*** * @param brand* @param weight* @param speed* 第二個參數和第三個參數同為double類型*/public Tank(String brand, double weight, double speed) {this.brand = brand;this.weight = weight;this.speed = speed;}public void introduce() {System.out.println("Tank information: brand:" + brand + ",weight:"+ weight + "KG,speed:" + speed + "km/h");}}第二個參數和第三個參數同為double類型,所以Spring無法確定type為double到底對應constructor-arg中的哪個,但是通過顯示指定參數的索引能消除這種不確定性。
配置文件
<?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="tank" class="com.xgj.ioc.inject.construct.index.Tank"><constructor-arg index="0" value="T72" /><constructor-arg index="1" value="20000" /><constructor-arg index="2" value="300" /></bean></beans>構造函數的一個參數索引為0,第二個為1,以此類推
測試類
package com.xgj.ioc.inject.construct.index;import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;public class ConstructInjectTest {public static void main(String[] args) {ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:com/xgj/ioc/inject/construct/index/tank_index_match.xml");Tank tank = ctx.getBean("tank", Tank.class);tank.introduce();}}運行結果:
聯合使用類型和索引匹配入參
有時候需要type和 index聯合使用才能確定配置項和構造函數入參的對應關系。
POJO 對象
package com.xgj.ioc.inject.construct.joint;public class Tank {private String brand;private double weight;private double speed;// 載人數量private int manned;/*** * @param brand* @param weight* @param speed* 第二個參數和第三個參數同為double類型*/public Tank(String brand, double weight, double speed) {this.brand = brand;this.weight = weight;this.speed = speed;}/*** * @param brand* @param weight* @param manned*/public Tank(String brand, double weight,int manned){this.brand =brand;this.weight = weight;this.manned = manned;}public void introduce() {System.out.println("Tank information: brand:" + brand + ",weight:"+ weight + "KG,speed:" + speed + "km/h");}public void introduce2() {System.out.println("Tank information: brand:" + brand + ",weight:"+ weight + "KG,manned:" + manned + "/person");}}配置文件
<?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="tank" class="com.xgj.ioc.inject.construct.joint.Tank"><constructor-arg type="java.lang.String" index="0" value="T72" /><constructor-arg type="double" index="1" value="25000" /><constructor-arg type="int" index="2" value="3" /></bean></beans>如果僅僅根據index來配置,Spring無法知道第三個入參配置的類型究竟是int還是double ,因此需要明確指出第三個入參的類型以消除歧義。
事實上,constructor-arg中前兩個的type屬性可以去掉、
當然了,也可以直接用type來判斷。
測試類
package com.xgj.ioc.inject.construct.joint;import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;public class ConstructInjectTest {public static void main(String[] args) {ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:com/xgj/ioc/inject/construct/joint/tank_joint_match.xml");Tank tank = ctx.getBean("tank", Tank.class);tank.introduce2();}}運行結果:
通過自身反射類型匹配入參
如果Bean構造函數入參的類型是可辯別的(非基礎數據類型且入參類型各不相同),由于Java反射機制可以獲取構造函數的入參類型,即使構造函數注入的配置不提供類型和索引的信息,Spring依然可以正確的完成構造函數的注入工作。
例子:
POJO類-Tank
package com.xgj.ioc.inject.construct.reflect;public class Tank {public void attack() {System.out.println("tank begins to attack");}}POJO類-Plane
package com.xgj.ioc.inject.construct.reflect;public class Plane {public void attack(){System.out.println("plane begins to attack");}}配置文件
<?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="plane" class="com.xgj.ioc.inject.construct.reflect.Plane"/><bean id="tank" class="com.xgj.ioc.inject.construct.reflect.Tank"/><bean id="commander" class="com.xgj.ioc.inject.construct.reflect.Commander"><!-- 沒有設置type和index屬性,通過入參值的類型完成映射匹配 --><constructor-arg value="XGJ"/><constructor-arg ref="plane"/><constructor-arg ref="tank"/></bean></beans>解析:
由于plane tank name入參的類型都是可辨別的,所以無需再構造函數注入的配置時指定constructor-arg的type和index,因此可以采用如上的簡易配置方式
測試類
package com.xgj.ioc.inject.construct.reflect;public class Commander {private Plane plane;private Tank tank;private String name;public Commander(Plane plane, Tank tank, String name) {super();this.plane = plane;this.tank = tank;this.name = name;}public void direct(){System.out.println("Commamder name:" + name);System.out.println("Commnader begins to direct the army");plane.attack();tank.attack();}} package com.xgj.ioc.inject.construct.reflect;import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;public class ConstructInjectTest {public static void main(String[] args) {ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:com/xgj/ioc/inject/construct/reflect/beans.xml");Commander commander = ctx.getBean("commander", Commander.class);commander.direct();}}運行結果
循環依賴問題
Spring容器能夠對構造函數配置的Bean進行實例化有一個前提:Bean構造函數入參所引用的對象必須已經準備就緒。
鑒于這個機制,如果兩個Bean都采用構造函數注入,并且都通過構造函數入參引用對方,就會發生類屬于線程死鎖的的循環依賴問題。
舉個例子說明一下(飛行員和飛機):
package com.xgj.ioc.inject.construct.loop;public class Pilot {private String pilotNname;private Plane plane;public Pilot(String pilotNname, Plane plane) {super();this.pilotNname = pilotNname;this.plane = plane;}public void drivePlane() {plane.fly();}} package com.xgj.ioc.inject.construct.loop;public class Plane {private Pilot pilot;private String planeBrand;public Plane(Pilot pilot, String planeBrand) {super();this.pilot = pilot;this.planeBrand = planeBrand;}public void fly() {System.out.println("Plane :" + planeBrand + " is reday to fly");} } <?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="pilot" class="com.xgj.ioc.inject.construct.loop.Pilot"><constructor-arg ref="plane"/><constructor-arg value="F35"/></bean><bean id="plane" class="com.xgj.ioc.inject.construct.loop.Plane"><constructor-arg ref="pilot"/><constructor-arg value="XGJ"/></bean> </beans> package com.xgj.ioc.inject.construct.loop;import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;public class ConstructInjectTest {public static void main(String[] args) {ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:com/xgj/ioc/inject/construct/loop/beans.xml");Pilot pilot = ctx.getBean("pilot", Pilot.class);pilot.drivePlane();}}運行結果:
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'pilot' defined in class path resource [com/xgj/ioc/inject/construct/loop/beans.xml]: Cannot resolve reference to bean 'plane' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'plane' defined in class path resource [com/xgj/ioc/inject/construct/loop/beans.xml]: Cannot resolve reference to bean 'pilot' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'pilot': Requested bean is currently in creation: Is there an unresolvable circular reference?at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:359) ....... ....... ....... org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)at com.xgj.ioc.inject.construct.loop.ConstructInjectTest.main(ConstructInjectTest.java:10) Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'plane' defined in class path resource [com/xgj/ioc/inject/construct/loop/beans.xml]: Cannot resolve reference to bean 'pilot' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'pilot': Requested bean is currently in creation: Is there an unresolvable circular reference?at ................. 29 more如何解決呢? 構造函數注入修改為 屬性注入即可。
如下所示:
只需要修改beans.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"><bean id="pilot" class="com.xgj.ioc.inject.construct.loopsolve.Pilot"><property name="pilotNname" value="XGJ"></property><property name="plane" ref="plane"></property></bean><bean id="plane" class="com.xgj.ioc.inject.construct.loopsolve.Plane"><property name="planeBrand" value="F35"></property><property name="pilot" ref="pilot"></property></bean></beans>運行結果
工廠方法注入
分為非靜態工廠方法和靜態工廠方法。
對于一個全新開發的應用來說,我們不推薦使用工廠方法的注入方式。因為工廠方法需要額外的類和代碼,這些功能和業務并無關系。
選擇注入方式的考量
仁者見仁 智者見智,并無定論。在合適的場景下使用合適的注入方式。
總結
以上是生活随笔為你收集整理的Spring-依赖注入的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring-Spring配置概述
- 下一篇: Spring-注入参数详解-[字面值及引