在我面試的時候,常會問面試者一個問題,就是依賴注入有幾種方式,發現面試者的回答五花八門,有回答兩種的,也有回答三種的,四種的。其實正確的答案是兩種:構造器注入和setter注入。
 提到依賴注入,就不能不說裝配。有些初學者總是會把這兩個概念搞混,這個博文就是來跟大家討論這兩個概念以及其中詳細的原理。
 依賴注入的本質就是裝配,裝配是依賴注入的具體行為。這就是兩者的關系。例如:
 <bean id="hello" class="com.maven.Hello"><constructor-arg value="hello" /></bean>這是使用構造器注入來裝配bean。
 <bean id="hello" class="com.maven.Hello" p:hello="hello" />這是使用setter注入,p是spring的名稱空間,可以用來代替<property>標簽。
 以上就是兩種依賴注入方式。上面的注入只是基本類型的注入。下面介紹一下常用的注入配置:
 1.構造器注入對象屬性
 <bean id="text" class="com.maven.Text" />
 <bean id="hello" class="com.maven.Hello"><constructor-arg ref="text" /></bean>
 2. 屬性注入對象屬性
 <bean id="hello" class="com.maven.Hello"><property name="text" ref="text" /></bean>
 3. 屬性為List類型或數組類型屬性注入
 <bean id="hello" class="com.maven.Hello">
 <property name="persons">
 <list>
 <ref bean="zhangsan" />
 <ref bean="zhangsan" />
 </list>
 </property>
 </bean>
 list元素的成員也可以是<value>,<bean>,<null />,其中<bean>是用來裝配匿名bean的,<null />是用來裝配null值的。匿名bean會在下面介紹。
 4.屬性為set類型的屬性注入
 set類型與list類型注入是一樣的,只是標簽改成<set>就可以了。並且里面的元素是不能重復的。
 5. 屬性為map類型的屬性注入
 <bean id="hello" class="com.maven.Hello">
 <property name="article">
 <map>
 <entry key="title" value-ref="text" />
 <entry key="title" value-ref="text" />
 </map>
 </property>
 </bean>
 map類型的鍵和值可以是任何類型,key-ref用來引用鍵是bean的,value-ref用來引用值是bean的
 6. 屬性為Properties類型的屬性注入
 <bean id="hello" class="com.maven.Hello">
 <property name="article">
 <props>
 <prop key="title">I LOVE YOU</prop>
 <prop key="title">I HATE YOU</prop>
 </props>
 </property>
 </bean>
 Properties類型的元素為props,每一個鍵值標簽是prop,注意要與property標簽進行區分。而且Properties類型的鍵和值必須都是String類型的。並且值是用<prop>標簽內容表示的,而不是用value屬性。
最后說一下匿名bean,這個比較少用。匿名bean跟匿名內部類是一樣的,但是匿名bean不需要實現接口,並且也只能用一次,所以<bean>標簽中不用寫id屬性。例如我們用匿名bean來作為屬性注入時:
 <property name="text"><bean class="com.maven.Text" /></property>。匿名bean沒有id屬性,因為匿名bean只能被使用一次,加上id屬性沒有意義。
 好了,說到這里,大家可以看到用xml裝配bean是一件很繁瑣的事情,而且我們還要找到對應類型的bean才能裝配。
 首先,確定一下裝配的概念。《spring實戰》中給裝配下了一個定義:創建應用對象之間協作關系的行為稱為裝配。也就是說當一個對象的屬性是另一個對象時,實例化時,需要為這個對象屬性進行實例化。這就是裝配。如果一個對象只通過接口來表明依賴關系,那么這種依賴就能夠在對象本身毫不知情的情況下,用不同的具體實現進行切換。但是這樣會存在一個問題,在傳統的依賴注入配置中,我們必須要明確要給屬性裝配哪一個bean的引用,一旦bean很多,就不好維護了。基於這樣的場景,spring使用注解來進行自動裝配,解決這個問題。自動裝配就是開發人員不必知道具體要裝配哪個bean的引用,這個識別的工作會由spring來完成。與自動裝配配合的還有“自動檢測”,這 個動作會自動識別哪些類需要被配置成bean,進而來進行裝配。這樣我們就明白了,自動裝配是為了將依賴注入“自動化”的一個簡化配置的操作。
 裝配分為四種:byName, byType, constructor, autodetect。byName就是會將與屬性的名字一樣的bean進行裝配。byType就是將同屬性一樣類型的bean進行裝配。constructor就是通過構造器來將類型與參數相同的bean進行裝配。autodetect是constructor與byType的組合,會先進行constructor,如果不成功,再進行byType。具體選擇哪一種裝配方式,需要配置<bean>標簽的autowire屬性,如果沒有配置,默認是byName類型,就是會根據屬性的名字來進行自動裝配。上面最常用的還是byName和byType。自動裝配時,裝配的bean必須是唯一與屬性進行吻合的,不能多也不能少,有且只有一個可以進行裝配的bean,才能自動裝配成功。否則會拋出異常。如果要統一所有bean的自動裝配類型,可以在<beans>標簽中配置default-autowire屬性。當然如果配置了autowire屬性,我們依然可以手動裝配屬性,手動裝配會覆蓋自動裝配。
 以上是通過xml配置的方式實現自動裝配的,spring2.5之后提供了注解方式的自動裝配。但是要使用這些注解,需要在配置文件中配置<context:annotation-config />。只有加上這一配置,才可以使用注解進行自動裝配,默認情況下基於注解的裝配是被禁用的。
 常用的自動裝配注解有以下幾種:@Autowired,@Resource,@Inject,@Qualifier,@Named。@Autowired注解是byType類型的,這個注解可以用在屬性上面,setter方面上面以及構造器上面。使用這個注解時,就不需要在類中為屬性添加setter方法了。但是這個屬性是強制性的,也就是說必須得裝配上,如果沒有找到合適的bean能夠裝配上,就會拋出異常。這時可以使用required=false來允許可以不被裝配上,默認值為true。當required=true時,@Autowired要求必須裝配,但是在沒有bean能裝配上時,就會拋出異常:NoSuchBeanDefinitionException,如果required=false時,則不會拋出異常。另一種情況是同時有多個bean是一個類型的,也會拋出這個異常。此時需要進一步明確要裝配哪一個Bean,這時可以組合使用@Qualifier注解,值為Bean的名字即可。@Qualifier注解使用byName進行裝配,這樣可以在多個類型一樣的bean中,明確使用哪一個名字的bean來進行裝配。@Qualifier注解起到了縮小自動裝配候選bean的范圍的作用。注意:@Autowired注解是spring提供的,所以會依賴spring的包。還有一個byType的注解@Inject,與@Autowired注解作用一樣,也是byType類型,而且是java ee提供的,完全可以代替@Autowired注解,但是@Inject必須是強制裝配的,沒有required屬性,也就是不能為null,如果不存在匹配的bean,會拋出異常。@Autowired與@Qualifier可以組合使用,@Inject也有一個組合的注解,就是@Named注解,與@Qualifier作用一樣,也是byName,但是不是spring的,是java ee標准的。這樣就出現了兩套自動裝配的注解組合,@Autowired與@Qualifier是spring提供的,@Inject與@Named是java ee的。但是@Qualifier注解在java ee中也有一樣,作用與spring的@Qualifier注解一模一樣,只是所在的包不一樣。不過建議大家使用spring的。最后還有一個@Resouce注解, 這個注解也是java ee的,也是byName類型的,原理同@Qualifier和@Named是一樣的。
 最后說一說,自動檢測配置,也是springmvc中最牛的一項功能。只要一個配置<context:component-scan base-package="">,base-package屬性指定要自動檢測掃描的包。
該配置會自動掃描指定的包及其子包下面被構造型注解標注的類,並將這些類注冊為spring bean,這樣就不用在配置文件一個一個地配置成bean標簽。構造型注解包括:@Controller,@Components,@Service,@Repository和使用@Component標注的自定義注解。生成的bean的ID默認為類的非限定名,也就是把類的名字的首字母換成小寫。可以在這些注解的值中寫名bean id的值,如@Controller("helloworld")。如果你想細化包被掃描的范圍,可以使用<context:include-filter>和<context:exclude-filter>。具體使用方法這里不再詳說。注意,沒有被掃描到的類是不能注冊為bean,也就不能被用來裝配其他類。所以這個配置的base-package的范圍非常重要。
本文選自西安樓鳳
