1. 程式人生 > >spring中自動注入resource和autowire的區別

spring中自動注入resource和autowire的區別

Spring中Autowired註解,Resource註解和xml default-autowire工作方式異同
2012/11/07 17:25:28 No Comments
Tags: autowire, default-autowire, java, resource, spring, 原始碼分析 Posted :java開發, 開源框架, 程式設計開發

前面說到了關於在xml中有提供default-autowire的配置資訊,從spring 2.5開始,spring又提供了一個Autowired以及javaEE中標準的Resource註釋,都好像可以實現類似的自動注入。那麼是不是每個都實現同樣的方式呢,這裡面的幾個配置到底有哪些異同點。哪個更全,哪個更優先,這些都需要對spring的內部原理有詳細的瞭解才可以進行了解。
在以下文章時,首先有幾個概念需要列出:
欄位名稱:即fieldName,這個即propertyDescriper的getPropertyName返回資訊。
setter名稱:即方法setter除set之外的名稱,如setAbc,則名稱為abc,這裡的abc不一定和fieldName相同。
引數名稱:即在引數中所定義的引數的名稱,如setAbc(Abc a123)。這裡的引數名稱就是a123。
本文所使用spring版本為spring3.0.2。

處理類和處理順序異同

default-autowire是在xml中進行配置的,而這個配置從spring初始就提供了。而Autowired註解,則是從2.5自支援以java1.5之後才出現的,這就必然導致對相應的處理以及邏輯是不同的。那麼每個方式的處理順序是怎樣的呢,從我寫的文章:Spring中獲取一個bean的流程-2.也可以由下面的程式碼得出:
if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ||
mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
MutablePropertyValues newPvs = new MutablePropertyValues(pvs);

// Add property values based on autowire by name if applicable.
if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) {
autowireByName(beanName, mbd, bw, newPvs);
}

// Add property values based on autowire by type if applicable.
if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
autowireByType(beanName, mbd, bw, newPvs);
}

pvs = newPvs;
}
......
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);

以上程式碼來源於類AbstractAutowireCapableBeanFactory的populateBean方法。從上可以看出,spring首先處理在bean定義上的autowire屬性,然後再處理後面的InstantiationAwareBeanPostProcessor類。首先bean定義上的autowire屬性,可以來自於<bean>定義時autowire屬性,也可以來自於整個xml定義中<beans>節點中的default-autowire屬性。

那麼@Autowired註解和@Resource註解在哪兒處理呢,看上面的程式碼,有個InstantiationAwareBeanPostProcessor類,如果你仔細檢視裡面的實現,你可以發現裡面即為處理相應註解類的實現。而這些註解類,只要在xml中啟用了<context:annotation-config/>,即可以開啟這些類了。而我們的Autowired註解,由AutowiredAnnotationBeanPostProcessor來進行處理,而Resource類,則由CommonAnnotationBeanPostProcessor進行處理。

處理內容和處理範圍異同

xml中default-autowire配置

首先,針對於xml配置中的default-autowire配置,我們都知道byName是通過name注入而byType是通過型別注入。byType沒有好爭議的,是根據型別從所有bean查詢滿足條件的bean,如果找到一個,則使用此bean。但是如果沒有找到,則不會報錯,但是如果發現有2個以上的侯選者,則會報No unique bean of type的錯誤資訊。

針對於byName,則是根據propertyDescriptor,即滿足bean規範的欄位資訊進行注入。之所以這裡重點提bean規範,請看以下程式碼:


private TxInterface tx2;

public TxInterface getTx3() {
return tx2;
}

public void setTx5(TxInterface tx3) {
this.tx2 = tx3;
}

這裡是不會進行任何注入的,因為裡面的tx2,根本不滿足bean規範。但如果將方法setTx5修改為setTx2,則就滿足bean規範,就會進行byName注入了。

@Autowired註解

再來看Autowired註解,有的人說autowired註解是按照byType方式進行配置,其實這個說法是錯的,至少是不完善的。為什麼呢,這需要我們來檢視整個autowire的流程,如以下程式碼示:

Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (matchingBeans.isEmpty()) {
if (descriptor.isRequired()) {
raiseNoSuchBeanDefinitionException(type, "", descriptor);
}
return null;
}
if (matchingBeans.size() > 1) {
String primaryBeanName = determinePrimaryCandidate(matchingBeans, descriptor);
if (primaryBeanName == null) {
throw new NoSuchBeanDefinitionException(type, "expected single matching bean but found " +
matchingBeans.size() + ": " + matchingBeans.keySet());
}
if (autowiredBeanNames != null) {
autowiredBeanNames.add(primaryBeanName);
}
return matchingBeans.get(primaryBeanName);
}
// We have exactly one match.
Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
if (autowiredBeanNames != null) {
autowiredBeanNames.add(entry.getKey());
}
return entry.getValue();

以上程式碼來自於類DefaultListableBeanFactory的doResolveDependency方法。這是由類AutowiredAnnotationBeanPostProcessor類通過呼叫inject方法時,需要通過呼叫beanFactory.resolveDependency(descriptor, beanName, autowiredBeanNames, typeConverter)來解析引用資訊。我們仔細看以上的邏輯,可以從下面的順序進行處理。

首先根據型別找到所有可以滿足條件的bean
判斷bean長度,如果沒有,則根據@autowired中的required屬性進行判斷是否丟擲異常(預設為true)
如果多於一個,則嘗試尋找最優的那一個,如果最優的未找到,則丟擲異常
如果只有一個,則直接使用此bean

這個邏輯與byType類似,但還不完全相同。不相同的則在第3點處理上,byType如果多於一個,則直接丟擲異常。而這裡有一個尋找最優bean的過程。即方法determinePrimaryCandidate的實現。實現程式碼不再列出。但根據@Autowired所放置位置有所不同。

放置在setter方法上,則使用MethodParameter的parameterName進行查詢,請注意這裡的parameterName。這個屬性只有在編譯時保留了debug的localVariable才會存在,否則即為null屬性。這個屬性即引數的名稱。如果localVariable不存在,則直接退化為byType。如果有,就按照引數名稱進行查詢。這裡的引數名稱不是setter後面的名稱,也不是欄位名。如以下程式碼所示:
1

public void setTx2(TxInterface tx1) {this.tx2 = tx1;}

這裡的名稱為tx1,而不是tx2。

放置在欄位上,則直接使用欄位名稱。進行查詢。

@Resource註解

最後看Resource註解,也有的人說resource是按byName註解,即就完全錯了。實際上Resource根本不會走byName方式,我們來看@Resource如何尋找一個bean。預設在不寫Resource(name)的情況下:

String name = element.name;

if (this.fallbackToDefaultTypeMatch && element.isDefaultName &&
factory instanceof AutowireCapableBeanFactory && !factory.containsBean(name)) {
autowiredBeanNames = new LinkedHashSet<String>();
resource = ((AutowireCapableBeanFactory) factory).resolveDependency(
element.getDependencyDescriptor(), requestingBeanName, autowiredBeanNames, null);
}
else {
resource = factory.getBean(name, element.lookupType);
autowiredBeanNames = Collections.singleton(name);
}

if (factory instanceof ConfigurableBeanFactory) {
ConfigurableBeanFactory beanFactory = (ConfigurableBeanFactory) factory;
for (String autowiredBeanName : autowiredBeanNames) {
beanFactory.registerDependentBean(autowiredBeanName, requestingBeanName);
}
}

return resource;

以上程式碼來自於類CommonAnnotationBeanPostProcessor中的autowireResource方法,是由此類通過getResourceToInject獲取將要注入的bean來呼叫的。上面的方法詳細描述了整個過程,如下所示:

獲取element的名稱,判斷beanFactory是否存在此name的bean
如果存在,則直接使用此name進行查詢
否則退化到預設的autowire查詢方式

從上面的第三步,可以看出,Resource在沒有根據name查詢到的情況下,會走Autowire的方式。所以,從範圍來看Resouce的查詢範圍比Autowire範圍更大。

再來看第1步,獲取element的名稱,這裡說是element的名稱,因為它的來源有2個地方。一是在resouce註解中配置的name屬性,第二就是setter名稱或者是field名稱(取決於@Resource的配置地點),。這裡說的是setter名稱,而不是屬性名稱,這就是需要注意的地方。來原始碼如下所示:

String resourceName = resource.name();
this.isDefaultName = !StringUtils.hasLength(resourceName);
if (this.isDefaultName) {
resourceName = this.member.getName();
if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
resourceName = Introspector.decapitalize(resourceName.substring(3));
}
}

來源於類ResourceElement的initAnnotation方法。因此,如果方法為如下所示:


@Resource
public void setTx2(TxInterface tx5) {
this.tx4 = tx5;
}

則獲取到的name就是tx2,而不是欄位名稱tx4。當然,上面的寫法不是標準的java bean規範寫法,但只是演示這種情況。那麼,在系統存在多個滿足type的情況下,如果上面的方法中的tx2的bean未找到,那麼接下來就尋找名為tx5(autowire規則),再找不到就該報Not Unique異常了。

值得注意的是,如果在使用resource時,根據resource的name找到了bean,但該bean並不是所需要的bean型別,則就要報型別不匹配錯誤了。即spring在查詢時,並沒有保證型別判斷,即你配置一個name的tx2的bean,但該型別即為TxInterface2而不是TxInterface,則spring在後期直接報異常,而不會fallback了。但Autowired註解則不會產生這種情況,因為它只會從滿足type的情況中的bean中查詢。

總結

在使用Autowired註解和Resource註解以及xml中的default-autowire註解時,需要詳細地瞭解每個註解的工作方式和工作範圍,在大多數情況下。這幾種方式都差不多,但在細節方面有差異。從筆者對於程式碼的嚴謹角度,我並不推薦在xml中配置default-autowire,因為這會導致所有的bean,不管需不需要注入,spring都會幫你注入。從一方面是好事,從另一方面就管得太多。如果確實要配置default-autowire,請再配置另一個屬性default-autowire-candidates,這個屬性可以固定default-autowire的範圍,比如*Service,可以只針對Service結尾的bean進行autowire包裝。

最後,@Autowire註解不是xml配置中的default-autowire-byType,而@Resource也不是@Autowire,更不是xml配置中的default-autowire-byName。不能夠簡單地混為一談。