為什麼Spring要支援Autowire(自動裝配)
先寫幾個類,首先定義一個Animal介面表示動物:
1 public interface Animal { 2 3 public void eat(); 4 5 }
寫一個Animal介面的實現Tiger類:
1 public class Tiger implements Animal { 2 3 @Override 4 public void eat() { 5 System.out.println("Tiger.eat()"); 6 } 7 8 @Override 9 public String toString() { 10 return "I'm a tiger"; 11 } 12 }
寫一個動物園類Zoo,持有Animal介面,表示動物園中有動物:
1 public class Zoo { 2 3 private Animal animal; 4 5 public Animal getAnimal() { 6 return animal; 7 } 8 9 public void setAnimal(Animal animal) { 10 this.animal = animal; 11 } 12 13 @Override 14 public String toString() { 15 if (animal == null) { 16 return null; 17 } 18 return animal.toString(); 19 } 20 21 }
配置一下spring檔案,由於這個功能研究的是Autowire,因此我命名為autowire.xml:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans 5 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> 6 7 <bean id="tiger" class="org.xrq.action.by.Tiger" /> 8 9 <bean id="zoo" class="org.xrq.action.by.Zoo"> 10 <property name="animal" ref="tiger" /> 11 </bean> 12 13 </beans>
Spring引入Autowire(自動裝配)機制就是為了解決<bean>標籤下<property>標籤過多的問題,<property>標籤過多會引發兩個問題:
- 如果一個Bean中要注入的物件過多,比如十幾二十個(這是很正常的),那將導致Spring配置檔案非常冗長,可讀性與維護性差
- 如果一個Bean中要注入的物件過多,配置麻煩且一不小心就容易出錯
因此,為了解決使用<property>標籤注入物件過多的問題,Spring引入自動裝配機制,簡化開發者配置難度,降低xml檔案配置大小。
使用Autowire去除<property>標籤
下面來看一下使用Autowire去除<property>,autowire有兩處點:
- 可以配置在<beans>根標籤下,表示對全域性<bean>起作用,屬性名為default-autowire
- 可以配置在<bean>標籤下,表示對當前<bean>起作用,屬性名為autowire
通常都是在<beans>根標籤下配置自動裝配比較多,default-autowire有四種取值:
- no:預設,即不進行自動裝配,每一個物件的注入比如依賴一個<property>標籤
- byName:按照beanName進行自動裝配,使用setter注入
- byType:按照bean型別進行自動裝配,使用setter注入
- constructor:與byType差不多,不過最終屬性通過建構函式進行注入
這裡研究的是去除<property>標籤,因此第一種不管;constructor裝配不太常用,因此這裡也不管,重點看最常用的byName與byType,至於具體使用哪種根據自己的業務特點進行相應的設定。
首先看一下byName,byName意為在spring配置檔案中查詢beanName與屬性名一致的bean並進行裝配,若型別不匹配則報錯,autowire.xml如果使用byName進行屬性裝配,那麼將改成以下的形式:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans 5 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd" 6 default-autowire="byName"> 7 8 <bean id="animal" class="org.xrq.action.by.Tiger" /> 9 10 <bean id="zoo" class="org.xrq.action.by.Zoo" /> 11 12 </beans>
看到Zoo中有一個名為animal的屬性,我將Tiger這個bean也命名為animal,由於Tiger是Animal介面的實現類,因此Spring可以找到beanName為animal的bean並自動裝配到Zoo的animal屬性中,這就是byName的自動裝配形式。
接著看一下byType的自動裝配形式,byType意為在spring配置檔案中查詢與屬性型別一致的bean並進行裝配,若有多個相同型別則報錯(下一部分講),autowire.xml如果使用byType進行屬性裝配,那麼將改成以下的形式:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans 5 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd" 6 default-autowire="byType"> 7 8 <bean id="tiger" class="org.xrq.action.by.Tiger" /> 9 10 <bean id="zoo" class="org.xrq.action.by.Zoo" /> 11 12 </beans>
將Tiger命名為tiger(將bean命名為類名首字母小寫也比較符合規範),由於Tiger是Animal介面的實現類,因此Spring找到了Tiger並自動裝配到Zoo的animal屬性中,這就是byType的自動裝配形式。
byType裝配出現多個相同型別的bean及解決方案
前面演示了,byType的裝配方式是在Spring配置檔案中尋找屬性型別與bean型別一致的bean,那麼有一個問題,就是如果屬性型別在Spring配置檔案中有多個相同型別的bean會出現什麼樣的情況?為了探究一下這個問題,先定義另外一個Animal介面的實現類,叫做lion:
1 public class Lion implements Animal { 2 3 @Override 4 public void eat() { 5 System.out.println("Lion.eat()"); 6 } 7 8 @Override 9 public String toString() { 10 return "I'm a lion"; 11 } 12 }
接著,在Spring配置檔案中定義一下Lion這個類:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans 5 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd" 6 default-autowire="byType"> 7 8 <bean id="tiger" class="org.xrq.action.by.Tiger" /> 9 <bean id="lion" class="org.xrq.action.by.Lion" /> 10 11 <bean id="zoo" class="org.xrq.action.by.Zoo" /> 12 13 </beans>
執行一個測試類,結果為:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'zoo' defined in class path resource [spring/autowire.xml]:
Unsatisfied dependency expressed through bean property 'animal': : No unique bean of type [org.xrq.action.by.Animal] is defined: expected single matching bean but
found 2: [tiger, lion]; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [org.xrq.action.by.Animal] is
defined: expected single matching bean but found 2: [tiger, lion] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1166) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1058) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:516) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:455) at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:293)
意思很明顯:想要通過byType方式為animal進行裝配卻找到了兩個符合要求的bean,分別為tiger與lion,這導致了沒有唯一的bean可以對animal進行裝配。
這個問題有兩種解決方案,假如現在我要裝配的是lion這個bean,第一種解決方案是將不需要進行自動裝配的bean進行排除,對不需要進行自動裝配的bean設定屬性autowire-candidate="false"即可:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans 5 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd" 6 default-autowire="byType"> 7 8 <bean id="tiger" class="org.xrq.action.by.Tiger" autowire-candidate="false" /> 9 <bean id="lion" class="org.xrq.action.by.Lion" /> 10 11 <bean id="zoo" class="org.xrq.action.by.Zoo" /> 12 13 </beans>
candidate顧名思義,即候選人的意思,autowire-candidate="false"即這個bean我不想讓它作為自動裝配的候選者,既然tiger不是自動裝配的候選者,那麼animal型別在Spring容器中能自動裝配的也就只有一個lion了,Spring自動裝配lion,不會有問題。
第一種思路是排除那些不需要作為自動裝配候選者的bean,第二種思路就從相反邏輯出發,設定當發現有多個候選者的時候優先使用其中的哪個候選者,對要作為自動裝配候選者的bean設定primary="true"即可:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans 5 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd" 6 default-autowire="byType"> 7 8 <bean id="tiger" class="org.xrq.action.by.Tiger" /> 9 <bean id="lion" class="org.xrq.action.by.Lion" primary="true" /> 10 11 <bean id="zoo" class="org.xrq.action.by.Zoo" /> 12 13 </beans>
這種方式同樣也可以將lion裝配到animal屬性中而不會報錯。