注解注入顧名思義就是通過注解來實現注入,Spring和注入相關的常見注解有Autowired、Resource、Qualifier、Service、Controller、Repository、Component。
Autowired是自動注入,自動從spring的上下文找到合適的bean來注入
Resource用來指定名稱注入
Qualifier和Autowired配合使用,指定bean的名稱
Service,Controller,Repository分別標記類是Service層類,Controller層類,數據存儲層的類,spring掃描注解配置時,會標記這些類要生成bean。
Component是一種泛指,標記類是組件,spring掃描注解配置時,會標記這些類要生成bean。
Spring對於Bean的依賴注入,支持多種注解方式:
1
2
3
4
5
6
7
8
9
|
@Resource
javax.annotation
JSR250 (Common Annotations
for
Java)
@Inject
javax.inject
JSR330 (Dependency Injection
for
Java)
@Autowired
org.springframework.bean.factory
Spring
|
直觀上看起來,@Autowired是Spring提供的注解,其他幾個都是JDK本身內建的注解,Spring對這些注解也進行了支持。但是使用起來這三者到底有什么區別呢?筆者經過方法的測試,發現一些有意思的特性。
區別總結如下:
一、@Autowired有個required屬性,可以配置為false,這種情況下如果沒有找到對應的bean是不會拋異常的。@Inject和@Resource沒有提供對應的配置,所以必須找到否則會拋異常。
二、 @Autowired和@Inject基本是一樣的,因為兩者都是使用AutowiredAnnotationBeanPostProcessor來處理 依賴注入。但是@Resource是個例外,它使用的是CommonAnnotationBeanPostProcessor來處理依賴注入。當然,兩者 都是BeanPostProcessor。
1
2
3
4
5
6
7
8
9
|
@Autowired
和
@Inject
- 默認 autowired by type
- 可以 通過
@Qualifier
顯式指定 autowired by qualifier name。
- 如果 autowired by type 失敗(找不到或者找到多個實現),則退化為autowired by field name
@Resource
- 默認 autowired by field name
- 如果 autowired by field name失敗,會退化為 autowired by type
- 可以 通過
@Qualifier
顯式指定 autowired by qualifier name
- 如果 autowired by qualifier name失敗,會退化為 autowired by field name。但是這時候如果 autowired by field name失敗,就不會再退化為autowired by type了。
|
TIPS Qualified name VS Bean name
在Spring設計中,Qualified name並不等同於Bean name,后者必須是唯一的,但是前者類似於tag或者group的作用,對特定的bean進行分類。可以達到getByTag(group)的效果。對 於XML配置的bean,可以通過id屬性指定bean name(如果沒有指定,默認使用類名首字母小寫),通過標簽指定qualifier name:
1
2
3
4
|
<bean id=
"lamborghini"
class
=
"me.arganzheng.study.spring.autowired.Lamborghini"
>
<qualifier value=
"luxury"
/>
<!-- inject any dependencies required by
this
bean -->
</bean>
|
如果是通過注解方式,那么可以通過@Qualifier注解指定qualifier name,通過@Named或者@Component(@Service,@Repository等)的value值指定bean name:
1
2
3
4
|
@Component
(
"lamborghini"
)
@Qualifier
(
"luxury"
)
public
class
Lamborghini
implements
Car {
}
|
或者
1
2
3
4
5
|
@Component
@Named
(
"lamborghini"
)
@Qualifier
(
"luxury"
)
public
class
Lamborghini
implements
Car {
}
|
同樣,如果沒有指定bean name,那么Spring會默認是用類名首字母小寫(Lamborghini=>lamborghini)。
三、 通過Anotation注入依賴的方式在XML注入方式之前進行。如果對同一個bean的依賴同時使用了兩種注入方式,那么XML的優先。但是不同擔心通過Anotation注入的依賴沒法注入XML中配置的bean,依賴注入是在bean的注冊之后進行的。
四、目前的autowired by type方式(筆者用的是3.2.3.RELEASE版本),Spring的AutowiredAnnotationBeanPostProcessor 實現都是有”bug”的,也就是說@Autowired和@Inject都是有坑的(稱之為坑,不稱之為bug是因為貌似是故意的。。)。這是來源於線上 的一個bug,也是這邊文章的寫作原因。現場如下:
application-context.xml中有如下定義:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
<xml version=
"1.0"
encoding=
"UTF-8"
?>
<beans xmlns=
"http://www.springframework.org/schema/beans"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop=
"http://www.springframework.org/schema/aop"
xmlns:context=
"http://www.springframework.org/schema/context"
xmlns:util=
"http://www.springframework.org/schema/util"
xsi:schemaLocation="
http:
//www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http:
//www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http:
//www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http:
//www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd">
<context:annotation-config />
<context:component-scan base-
package
=
"me.arganzheng.study"
/>
<util:constant id=
"en"
static
-field=
"me.arganzheng.study.spring.autowired.Constants.Language.EN"
/>
<util:constant id=
"ja"
static
-field=
"me.arganzheng.study.spring.autowired.Constants.Language.JP"
/>
<util:constant id=
"ind"
static
-field=
"me.arganzheng.study.spring.autowired.Constants.Language.IND"
/>
<util:constant id=
"pt"
static
-field=
"me.arganzheng.study.spring.autowired.Constants.Language.PT"
/>
<util:constant id=
"th"
static
-field=
"me.arganzheng.study.spring.autowired.Constants.Language.TH"
/>
<util:constant id=
"ar"
static
-field=
"me.arganzheng.study.spring.autowired.Constants.Language.AR"
/>
<util:constant id=
"en-rIn"
static
-field=
"me.arganzheng.study.spring.autowired.Constants.Language.EN_RIN"
/>
<util:map id=
"languageChangesMap"
key-type=
"java.lang.String"
value-type=
"java.lang.String"
>
<entry key=
"pt"
value=
"pt"
/>
<entry key=
"br"
value=
"pt"
/>
<entry key=
"jp"
value=
"ja"
/>
<entry key=
"ja"
value=
"ja"
/>
<entry key=
"ind"
value=
"ind"
/>
<entry key=
"id"
value=
"ind"
/>
<entry key=
"en-rin"
value=
"en-rIn"
/>
<entry key=
"in"
value=
"en-rIn"
/>
<entry key=
"en"
value=
"en"
/>
<entry key=
"gb"
value=
"en"
/>
<entry key=
"th"
value=
"th"
/>
<entry key=
"ar"
value=
"ar"
/>
<entry key=
"eg"
value=
"ar"
/>
</util:map>
</beans>
|
其中static-field應用的常量定義在如下類中:
1
2
3
4
5
6
7
8
9
10
11
12
|
package
me.arganzheng.study.spring.autowired;
public
interface
Constants {
public
interface
Language {
public
static
final
String EN =
"CommonConstants.LANG_ENGLISH"
;
public
static
final
String JP =
"CommonConstants.LANG_JAPANESE"
;
public
static
final
String IND =
"CommonConstants.LANG_INDONESIAN"
;
public
static
final
String PT =
"CommonConstants.LANG_PORTUGUESE"
;
public
static
final
String TH =
"CommonConstants.LANG_THAI"
;
public
static
final
String EN_RIN =
"CommonConstants.LANG_ENGLISH_INDIA"
;
public
static
final
String AR =
"CommonConstants.LANG_Arabic"
;
}
}
|
然后如果我們在代碼中如下聲明依賴:
1
2
3
4
5
6
7
8
9
10
|
public
class
AutowiredTest
extends
BaseSpringTestCase {
@Autowired
private
Map<String, String> languageChangesMap;
@Test
public
void
testAutowired() {
notNull(languageChangesMap);
System.out.println(languageChangesMap.getClass().getSimpleName());
System.out.println(languageChangesMap);
}
}
|
Guess what,詭異的事情發生了!
運行結果如下:
1
2
|
LinkedHashMap
{en=CommonConstants.LANG_ENGLISH, ja=CommonConstants.LANG_JAPANESE, ind=CommonConstants.LANG_INDONESIAN, pt=CommonConstants.LANG_PORTUGUESE, th=CommonConstants.LANG_THAI, ar=CommonConstants.LANG_Arabic, en-rIn=CommonConstants.LANG_ENGLISH_INDIA}
|
也就是說Map
嚴重: Caught exception while allowing TestExecutionListener
1
2
3
4
5
6
7
8
9
|
[org.springframework.test.context.support.DependencyInjectionTestExecutionListener
@5c51ee0a
] to prepare test instance [me.arganzheng.study.spring.autowired.AutowiredTest
@6e301e0
]
org.springframework.beans.factory.BeanCreationException: Error creating bean with name
'me.arganzheng.study.spring.autowired.AutowiredTest'
: Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field:
private
java.util.Map me.arganzheng.study.spring.autowired.AutowiredTest.languageChangesMap; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [java.lang.String] found
for
dependency [map with value type java.lang.String]: expected at least
1
bean which qualifies as autowire candidate
for
this
dependency. Dependency annotations: {
@org
.springframework.beans.factory.annotation.Autowired(required=
true
)}
...
ed by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [java.lang.String] found
for
dependency [map with value type java.lang.String]: expected at least
1
bean which qualifies as autowire candidate
for
this
dependency. Dependency annotations: {
@org
.springframework.beans.factory.annotation.Autowired(required=
true
)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:
986
)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:
843
)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:
768
)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:
486
)
...
28
more
|
debug了一下,發現確實是Spring的一個bug。在DefaultListableBeanFactory的這個方法出問題了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
protected
Object doResolveDependency(DependencyDescriptor descriptor, Class<?> type, String beanName,
Set<String> autowiredBeanNames, TypeConverter typeConverter)
throws
BeansException {
...
else
if
(Map.
class
.isAssignableFrom(type) && type.isInterface()) {
Class<?> keyType = descriptor.getMapKeyType();
if
(keyType ==
null
|| !String.
class
.isAssignableFrom(keyType)) {
if
(descriptor.isRequired()) {
throw
new
FatalBeanException(
"Key type ["
+ keyType +
"] of map ["
+ type.getName() +
"] must be assignable to [java.lang.String]"
);
}
return
null
;
}
Class<?> valueType = descriptor.getMapValueType();
if
(valueType ==
null
) {
if
(descriptor.isRequired()) {
throw
new
FatalBeanException(
"No value type declared for map ["
+ type.getName() +
"]"
);
}
return
null
;
}
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType, descriptor);
if
(matchingBeans.isEmpty()) {
if
(descriptor.isRequired()) {
raiseNoSuchBeanDefinitionException(valueType,
"map with value type "
+ valueType.getName(), descriptor);
}
return
null
;
}
if
(autowiredBeanNames !=
null
) {
autowiredBeanNames.addAll(matchingBeans.keySet());
}
return
matchingBeans;
}
...
}
|
關鍵在這一句:Map
嚴重: Caught exception while allowing TestExecutionListener
1
2
3
4
5
6
7
8
|
[org.springframework.test.context.support.DependencyInjectionTestExecutionListener
@9476189
] to prepare test instance [me.arganzheng.study.spring.autowired.AutowiredTest
@2d546e21
]
...
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [java.lang.String] found
for
dependency [map with value type java.lang.String]: expected at least
1
bean which qualifies as autowire candidate
for
this
dependency. Dependency annotations: {
@org
.springframework.beans.factory.annotation.Autowired(required=
true
),
@org
.springframework.beans.factory.annotation.Qualifier(value=languageChangesMap)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:
986
)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:
843
)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:
768
)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:
486
)
...
28
more
|
debug了一下,發現跟沒有指定qualifie name是一樣的執行路徑。不是指定了bean name了嗎?為什么還是autowired by type呢?仔細查看了一下才發現。DefaultListableBeanFactory的doResolveDependency方法對首先對類型做 了區別:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
protected
Object doResolveDependency(DependencyDescriptor descriptor, Class<?> type, String beanName,
Set<String> autowiredBeanNames, TypeConverter typeConverter)
throws
BeansException {
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if
(value !=
null
) {
if
(value
instanceof
String) {
String strVal = resolveEmbeddedValue((String) value);
BeanDefinition bd = (beanName !=
null
&& containsBean(beanName) ? getMergedBeanDefinition(beanName) :
null
);
value = evaluateBeanDefinitionString(strVal, bd);
}
TypeConverter converter = (typeConverter !=
null
? typeConverter : getTypeConverter());
return
(descriptor.getField() !=
null
?
converter.convertIfNecessary(value, type, descriptor.getField()) :
converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
}
if
(type.isArray()) {
Class<?> componentType = type.getComponentType();
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType, descriptor);
if
(matchingBeans.isEmpty()) {
if
(descriptor.isRequired()) {
raiseNoSuchBeanDefinitionException(componentType,
"array of "
+ componentType.getName(), descriptor);
}
return
null
;
}
if
(autowiredBeanNames !=
null
) {
autowiredBeanNames.addAll(matchingBeans.keySet());
}
TypeConverter converter = (typeConverter !=
null
? typeConverter : getTypeConverter());
return
converter.convertIfNecessary(matchingBeans.values(), type);
}
else
if
(Collection.
class
.isAssignableFrom(type) && type.isInterface()) {
Class<?> elementType = descriptor.getCollectionType();
if
(elementType ==
null
) {
if
(descriptor.isRequired()) {
throw
new
FatalBeanException(
"No element type declared for collection ["
+ type.getName() +
"]"
);
}
return
null
;
}
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType, descriptor);
if
(matchingBeans.isEmpty()) {
if
(descriptor.isRequired()) {
raiseNoSuchBeanDefinitionException(elementType,
"collection of "
+ elementType.getName(), descriptor);
}
return
null
;
}
if
(autowiredBeanNames !=
null
) {
autowiredBeanNames.addAll(matchingBeans.keySet());
}
TypeConverter converter = (typeConverter !=
null
? typeConverter : getTypeConverter());
return
converter.convertIfNecessary(matchingBeans.values(), type);
}
else
if
(Map.
class
.isAssignableFrom(type) && type.isInterface()) {
Class<?> keyType = descriptor.getMapKeyType();
if
(keyType ==
null
|| !String.
class
.isAssignableFrom(keyType)) {
if
(descriptor.isRequired()) {
throw
new
FatalBeanException(
"Key type ["
+ keyType +
"] of map ["
+ type.getName() +
"] must be assignable to [java.lang.String]"
);
}
return
null
;
}
Class<?> valueType = descriptor.getMapValueType();
if
(valueType ==
null
) {
if
(descriptor.isRequired()) {
throw
new
FatalBeanException(
"No value type declared for map ["
+ type.getName() +
"]"
);
}
return
null
;
}
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType, descriptor);
if
(matchingBeans.isEmpty()) {
if
(descriptor.isRequired()) {
raiseNoSuchBeanDefinitionException(valueType,
"map with value type "
+ valueType.getName(), descriptor);
}
return
null
;
}
if
(autowiredBeanNames !=
null
) {
autowiredBeanNames.addAll(matchingBeans.keySet());
}
return
matchingBeans;
}
else
{
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
NoUniqueBeanDefinitionException(type, 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();
}
}
|
如果是Array,Collection或者Map,則根據集合類中元素的類型來進行autowired by type(Map使用value的類型)。為什么這么特殊處理呢?原來,Spring是為了達到這樣的目的:讓你可以一次注入所有符合類型的實現,也就是 說可以這樣子注入:
@Autowired
private List<Car> cars;
如果你的car有多個實現,那么都會注入進來,不會再報
1
2
3
|
org.springframework.beans.factory.NoSuchBeanDefinitionException:
No unique bean of type [me.arganzheng.study.spring.autowired.Car] is defined:
expected single matching bean but found
2
: [audi, toyota].
|
然而,上面的情況如果你用@Resource則不會有這個問題:
1
2
3
4
5
6
7
8
9
10
11
|
public
class
AutowiredTest
extends
BaseSpringTestCase {
@Resource
@Qualifier
(
"languageChangesMap"
)
private
Map<String, String> languageChangesMap;
@Test
public
void
testAutowired() {
assertNotNull(languageChangesMap);
System.out.println(languageChangesMap.getClass().getSimpleName());
System.out.println(languageChangesMap);
}
}
|
正常運行:
1
2
|
LinkedHashMap
{pt=pt, br=pt, jp=ja, ja=ja, ind=ind, id=ind, en-rin=en-rIn, in=en-rIn, en=en, gb=en, th=th, ar=ar, eg=ar}
|
當然,你如果不指定@Qualifier(“languageChangesMap”),同時field name不是languageChangesMap,那么還是一樣報錯的。
1
2
3
4
5
6
7
8
9
10
11
|
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [java.lang.String] found
for
dependency [map with value type java.lang.String]: expected at least
1
bean which qualifies as autowire candidate
for
this
dependency. Dependency annotations: {
@javax
.annotation.Resource(shareable=
true
, mappedName=, description=, name=, type=
class
java.lang.Object, authenticationType=CONTAINER, lookup=)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:
986
)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:
843
)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:
768
)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:
438
)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:
416
)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:
550
)
at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:
150
)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:
87
)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:
303
)
...
26
more
|
而且,@Resource也可以實現上面的List接收所有實現:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public
class
AutowiredTest
extends
BaseSpringTestCase {
@Resource
@Qualifier
(
"languageChangesMap"
)
private
Map<String, String> languageChangesMap;
@Resource
private
List<Car> cars;
@Test
public
void
testAutowired() {
assertNotNull(languageChangesMap);
System.out.println(languageChangesMap.getClass().getSimpleName());
System.out.println(languageChangesMap);
assertNotNull(cars);
System.out.println(cars.getClass().getSimpleName());
System.out.println(cars);
}
}
|
運行的妥妥的:
1
2
3
|
LinkedHashMap
{pt=pt, br=pt, jp=ja, ja=ja, ind=ind, id=ind, en-rin=en-rIn, in=en-rIn, en=en, gb=en, th=th, ar=ar, eg=ar}
ArrayList
|
[me.arganzheng.study.spring.autowired.Audi@579584da, me.arganzheng.study.spring.autowired.Toyota@19453122]
這是因為@Resource注解使用的是CommonAnnotationBeanPostProcessor處理器,跟 AutowiredAnnotationBeanPostProcessor不是同一個作者[/偷笑]。這里就不分析了,感興趣的同學可以自己看代碼研究 一下。
最終結論如下:
1、@Autowired和@Inject
autowired by type 可以 通過@Qualifier 顯式指定 autowired by qualifier name(非集合類。注意:不是autowired by bean name!)
如果 autowired by type 失敗(找不到或者找到多個實現),則退化為autowired by field name(非集合類)
2、@Resource
默認 autowired by field name
如果 autowired by field name失敗,會退化為 autowired by type
可以 通過@Qualifier 顯式指定 autowired by qualifier name
如果 autowired by qualifier name失敗,會退化為 autowired by field name。但是這時候如果 autowired by field name失敗,就不會再退化為autowired by type了
測試工程保存在GitHub上,是標准的maven工程,感興趣的同學可以clone到本地運行測試一下。
補充
有同事指出Spring官方文檔上有這么一句話跟我的結有點沖突:
However, although you can use this convention to refer to specific beans by name, @Autowired is fundamentally about type-driven injection with optional semantic qualifiers. This means that qualifier values, even with the bean name fallback, always have narrowing semantics within the set of type matches; they do not semantically express a reference to a unique bean id.
也就是說@Autowired即使加了@Qualifier注解,其實也是autowired by type。@Qualifier只是一個限定詞,過濾條件而已。重新跟進了一下代碼,發現確實是這樣子的。Spring設計的這個 @Qualifier name 並不等同於 bean name。他有點類似於一個tag。不過如果這個tag是唯一的化,那么其實效果上等同於bean name。實現上,Spring是先getByType,得到list candicates,然后再根據qualifier name進行過濾。
再定義一個蘭博基尼,這里使用@Qualifier指定:
1
2
3
4
5
6
7
|
package
me.arganzheng.study.spring.autowired;
import
org.springframework.beans.factory.annotation.Qualifier;
import
org.springframework.stereotype.Component;
@Component
@Qualifier
(
"luxury"
)
public
class
Lamborghini
implements
Car {
}
|
再定義一個勞斯萊斯,這里故意用@Named指定:
1
2
3
4
5
6
7
|
package
me.arganzheng.study.spring.autowired;
import
javax.inject.Named;
import
org.springframework.stereotype.Component;
@Component
@Named
(
"luxury"
)
public
class
RollsRoyce
implements
Car {
}
|
測試一下注入定義的豪華車:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package
me.arganzheng.study.spring.autowired;
import
static
junit.framework.Assert.assertNotNull;
import
java.util.List;
import
me.arganzheng.study.BaseSpringTestCase;
import
org.junit.Test;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.beans.factory.annotation.Qualifier;
/**
*
* @author zhengzhibin
*
*/
public
class
AutowiredTest
extends
BaseSpringTestCase {
@Autowired
@Qualifier
(
"luxury"
)
private
List<Car> luxuryCars;
@Test
public
void
testAutowired() {
assertNotNull(luxuryCars);
System.out.println(luxuryCars.getClass().getSimpleName());
System.out.println(luxuryCars);
}
}
|
運行結果如下:
1
2
|
ArrayList
[me.arganzheng.study.spring.autowired.Lamborghini
@66b875e1
, me.arganzheng.study.spring.autowired.RollsRoyce
@58433b76
]
|
補充:Autowiring modes
Spring支持四種autowire模式,當使用XML配置方式時,你可以通過autowire屬性指定。
1
2
3
4
|
no. (Default) No autowiring. Bean references must be defined via a ref element. Changing the
default
setting is not recommended
for
larger deployments, because specifying collaborators explicitly gives greater control and clarity. To some extent, it documents the structure of a system.
byName. Autowiring by property name. Spring looks
for
a bean with the same name as the property that needs to be autowired. For example,
if
a bean definition is set to autowire by name, and it contains a master property (that is, it has a setMaster(..) method), Spring looks
for
a bean definition named master, and uses it to set the property.
byType. Allows a property to be autowired
if
exactly one bean of the property type exists in the container. If more than one exists, a fatal exception is thrown, which indicates that you may not use byType autowiring
for
that bean. If there are no matching beans, nothing happens; the property is not set.
constructor. Analogous to byType, but applies to constructor arguments. If there is not exactly one bean of the constructor argument type in the container, a fatal error is raised.
|
如果使用@Autowired、@Inject或者@Resource注解的時候,則稍微復雜一些,會有一個失敗退化過程,並且引入了Qualifier。不過基本原理是一樣。