Spring9:Autowire(自動裝配)機制

五月的倉頡發表於2017-04-13

為什麼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屬性中而不會報錯。

相關文章