徹底搞明白Spring中的自動(dòng)裝配和Autowired注解的使用
一、自動(dòng)裝配
當(dāng)Spring裝配Bean屬性時(shí),有時(shí)候非常明確,就是需要將某個(gè)Bean的引用裝配給指定屬性。比如,如果我們的應(yīng)用上下文中只有一個(gè)org.mybatis.spring.SqlSessionFactoryBean類型的Bean,那么任意一個(gè)依賴SqlSessionFactoryBean的其他Bean就是需要這個(gè)Bean。畢竟這里只有一個(gè)SqlSessionFactoryBean的Bean。
為了應(yīng)對(duì)這種明確的裝配場(chǎng)景,Spring提供了自動(dòng)裝配(autowiring)。與其顯式的裝配Bean屬性,為何不讓Spring識(shí)別出可以自動(dòng)裝配的場(chǎng)景。
當(dāng)涉及到自動(dòng)裝配Bean的依賴關(guān)系時(shí),Spring有多種處理方式。因此,Spring提供了4種自動(dòng)裝配策略。
public interface AutowireCapableBeanFactory{
//無(wú)需自動(dòng)裝配
int AUTOWIRE_NO = 0;
//按名稱自動(dòng)裝配bean屬性
int AUTOWIRE_BY_NAME = 1;
//按類型自動(dòng)裝配bean屬性
int AUTOWIRE_BY_TYPE = 2;
//按構(gòu)造器自動(dòng)裝配
int AUTOWIRE_CONSTRUCTOR = 3;
//過(guò)時(shí)方法,Spring3.0之后不再支持
@Deprecated
int AUTOWIRE_AUTODETECT = 4;
}
Spring在AutowireCapableBeanFactory接口中定義了這幾種策略。其中,AUTOWIRE_AUTODETECT被標(biāo)記為過(guò)時(shí)方法,在Spring3.0之后已經(jīng)不再支持。
1、byName
它的意思是,把與Bean的屬性具有相同名字的其他Bean自動(dòng)裝配到Bean的對(duì)應(yīng)屬性中。聽(tīng)起來(lái)可能比較拗口,我們來(lái)看個(gè)例子。
首先,在User的Bean中有個(gè)屬性Role myRole,再創(chuàng)建一個(gè)Role的Bean,它的名字如果叫myRole,那么在User中就可以使用byName來(lái)自動(dòng)裝配。
public class User{
private Role myRole;
}
public class Role {
private String id;
private String name;
}
上面是Bean的定義,再看配置文件。
<bean id="myRole" class="com.viewscenes.netsupervisor.entity.Role"> <property name="id" value="1001"></property> <property name="name" value="管理員"></property> </bean> <bean id="user" class="com.viewscenes.netsupervisor.entity.User" autowire="byName"></bean>
如上所述,只要屬性名稱和Bean的名稱可以對(duì)應(yīng),那么在user的Bean中就可以使用byName來(lái)自動(dòng)裝配。那么,如果屬性名稱對(duì)應(yīng)不上呢?
2、byType
是的,如果不使用屬性名稱來(lái)對(duì)應(yīng),你也可以選擇使用類型來(lái)自動(dòng)裝配。它的意思是,把與Bean的屬性具有相同類型的其他Bean自動(dòng)裝配到Bean的對(duì)應(yīng)屬性中。
<bean class="com.viewscenes.netsupervisor.entity.Role"> <property name="id" value="1001"></property> <property name="name" value="管理員"></property> </bean> <bean id="user" class="com.viewscenes.netsupervisor.entity.User" autowire="byType"></bean>
還是上面的例子,如果使用byType,Role Bean的ID都可以省去。
3、constructor
它是說(shuō),把與Bean的構(gòu)造器入?yún)⒕哂邢嗤愋偷钠渌鸅ean自動(dòng)裝配到Bean構(gòu)造器的對(duì)應(yīng)入?yún)⒅小V档淖⒁獾氖牵哂邢嗤愋偷钠渌鸅ean這句話說(shuō)明它在查找入?yún)⒌臅r(shí)候,還是通過(guò)Bean的類型來(lái)確定。
構(gòu)造器中入?yún)⒌念愋蜑镽ole
public class User{
private Role role;
public User(Role role) {
this.role = role;
}
}
<bean id="user" class="com.viewscenes.netsupervisor.entity.User" autowire="constructor"></bean>
4、autodetect
它首先會(huì)嘗試使用constructor進(jìn)行自動(dòng)裝配,如果失敗再嘗試使用byType。不過(guò),它在Spring3.0之后已經(jīng)被標(biāo)記為@Deprecated。
5、默認(rèn)自動(dòng)裝配
默認(rèn)情況下,default-autowire屬性被設(shè)置為none,標(biāo)示所有的Bean都不使用自動(dòng)裝配,除非Bean上配置了autowire屬性。
如果你需要為所有的Bean配置相同的autowire屬性,有個(gè)辦法可以簡(jiǎn)化這一操作。
在根元素Beans上增加屬性default-autowire="byType"。
<beans default-autowire="byType">
Spring自動(dòng)裝配的優(yōu)點(diǎn)不言而喻。但是事實(shí)上,在Spring XML配置文件里的自動(dòng)裝配并不推薦使用,其中筆者認(rèn)為最大的缺點(diǎn)在于不確定性。或者除非你對(duì)整個(gè)Spring應(yīng)用中的所有Bean的情況了如指掌,不然隨著B(niǎo)ean的增多和關(guān)系復(fù)雜度的上升,情況可能會(huì)很糟糕
二、Autowired
從Spring2.5開(kāi)始,開(kāi)始支持使用注解來(lái)自動(dòng)裝配Bean的屬性。它允許更細(xì)粒度的自動(dòng)裝配,我們可以選擇性的標(biāo)注某一個(gè)屬性來(lái)對(duì)其應(yīng)用自動(dòng)裝配。
Spring支持幾種不同的應(yīng)用于自動(dòng)裝配的注解。
- Spring自帶的@Autowired注解。
- JSR-330的@Inject注解。
- JSR-250的@Resource注解。
我們今天只重點(diǎn)關(guān)注Autowired注解,關(guān)于它的解析和注入過(guò)程,請(qǐng)參考筆者Spring源碼系列的文章。Spring源碼分析(二)bean的實(shí)例化和IOC依賴注入
使用@Autowired很簡(jiǎn)單,在需要注入的屬性加入注解即可。
@Autowired UserService userService;
不過(guò),使用它有幾個(gè)點(diǎn)需要注意。
1、強(qiáng)制性
默認(rèn)情況下,它具有強(qiáng)制契約特性,其所標(biāo)注的屬性必須是可裝配的。如果沒(méi)有Bean可以裝配到Autowired所標(biāo)注的屬性或參數(shù)中,那么你會(huì)看到NoSuchBeanDefinitionException的異常信息。
public Object doResolveDependency(DependencyDescriptor descriptor, String beanName,
Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {
//查找Bean
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
//如果拿到的Bean集合為空,且isRequired,就拋出異常。
if (matchingBeans.isEmpty()) {
if (descriptor.isRequired()) {
raiseNoSuchBeanDefinitionException(type, "", descriptor);
}
return null;
}
}
看到上面的源碼,我們可以得到這一信息,Bean集合為空不要緊,關(guān)鍵isRequired條件不能成立,那么,如果我們不確定屬性是否可以裝配,可以這樣來(lái)使用Autowired。
@Autowired(required=false) UserService userService;
2、裝配策略
我記得曾經(jīng)有個(gè)面試題是這樣問(wèn)的:Autowired是按照什么策略來(lái)自動(dòng)裝配的呢?
關(guān)于這個(gè)問(wèn)題,不能一概而論,你不能簡(jiǎn)單的說(shuō)按照類型或者按照名稱。但可以確定的一點(diǎn)的是,它默認(rèn)是按照類型來(lái)自動(dòng)裝配的,即byType。
默認(rèn)按照類型裝配
關(guān)鍵點(diǎn)findAutowireCandidates這個(gè)方法。
protected Map<String, Object> findAutowireCandidates(
String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
//獲取給定類型的所有bean名稱,里面實(shí)際循環(huán)所有的beanName,獲取它的實(shí)例
//再通過(guò)isTypeMatch方法來(lái)確定
String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this, requiredType, true, descriptor.isEager());
Map<String, Object> result = new LinkedHashMap<String, Object>(candidateNames.length);
//根據(jù)返回的beanName,獲取其實(shí)例返回
for (String candidateName : candidateNames) {
if (!isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, descriptor)) {
result.put(candidateName, getBean(candidateName));
}
}
return result;
}
按照名稱裝配
可以看到它返回的是一個(gè)列表,那么就表明,按照類型匹配可能會(huì)查詢到多個(gè)實(shí)例。到底應(yīng)該裝配哪個(gè)實(shí)例呢?我看有的文章里說(shuō),可以加注解以此規(guī)避。比如@qulifier、@Primary等,實(shí)際還有個(gè)簡(jiǎn)單的辦法。
比如,按照UserService接口類型來(lái)裝配它的實(shí)現(xiàn)類。UserService接口有多個(gè)實(shí)現(xiàn)類,分為UserServiceImpl、UserServiceImpl2。那么我們?cè)谧⑷氲臅r(shí)候,就可以把屬性名稱定義為Bean實(shí)現(xiàn)類的名稱。
@Autowired UserService UserServiceImpl2;
這樣的話,Spring會(huì)按照byName來(lái)進(jìn)行裝配。首先,如果查到類型的多個(gè)實(shí)例,Spring已經(jīng)做了判斷。
public Object doResolveDependency(DependencyDescriptor descriptor, String beanName,
Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {
//按照類型查找Bean實(shí)例
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
//如果Bean集合為空,且isRequired成立就拋出異常
if (matchingBeans.isEmpty()) {
if (descriptor.isRequired()) {
raiseNoSuchBeanDefinitionException(type, "", descriptor);
}
return null;
}
//如果查找的Bean實(shí)例大于1個(gè)
if (matchingBeans.size() > 1) {
//找到最合適的那個(gè),如果沒(méi)有合適的。。也拋出異常
String primaryBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (primaryBeanName == null) {
throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet());
}
if (autowiredBeanNames != null) {
autowiredBeanNames.add(primaryBeanName);
}
return matchingBeans.get(primaryBeanName);
}
}
可以看出,如果查到多個(gè)實(shí)例,determineAutowireCandidate方法就是關(guān)鍵。它來(lái)確定一個(gè)合適的Bean返回。其中一部分就是按照Bean的名稱來(lái)匹配。
protected String determineAutowireCandidate(Map<String, Object> candidateBeans,
DependencyDescriptor descriptor) {
//循環(huán)拿到的Bean集合
for (Map.Entry<String, Object> entry : candidateBeans.entrySet()) {
String candidateBeanName = entry.getKey();
Object beanInstance = entry.getValue();
//通過(guò)matchesBeanName方法來(lái)確定bean集合中的名稱是否與屬性的名稱相同
if (matchesBeanName(candidateBeanName, descriptor.getDependencyName())) {
return candidateBeanName;
}
}
return null;
}
最后我們回到問(wèn)題上,得到的答案就是:@Autowired默認(rèn)使用byType來(lái)裝配屬性,如果匹配到類型的多個(gè)實(shí)例,再通過(guò)byName來(lái)確定Bean。
3、主和優(yōu)先級(jí)
上面我們已經(jīng)看到了,通過(guò)byType可能會(huì)找到多個(gè)實(shí)例的Bean。然后再通過(guò)byName來(lái)確定一個(gè)合適的Bean,如果通過(guò)名稱也確定不了呢?
還是determineAutowireCandidate這個(gè)方法,它還有兩種方式來(lái)確定。
protected String determineAutowireCandidate(Map<String, Object> candidateBeans,
DependencyDescriptor descriptor) {
Class<?> requiredType = descriptor.getDependencyType();
//通過(guò)@Primary注解來(lái)標(biāo)識(shí)Bean
String primaryCandidate = determinePrimaryCandidate(candidateBeans, requiredType);
if (primaryCandidate != null) {
return primaryCandidate;
}
//通過(guò)@Priority(value = 0)注解來(lái)標(biāo)識(shí)Bean value為優(yōu)先級(jí)大小
String priorityCandidate = determineHighestPriorityCandidate(candidateBeans, requiredType);
if (priorityCandidate != null) {
return priorityCandidate;
}
return null;
}
Primary
它的作用是看Bean上是否包含@Primary注解,如果包含就返回。當(dāng)然了,你不能把多個(gè)Bean都設(shè)置為@Primary,不然你會(huì)得到NoUniqueBeanDefinitionException這個(gè)異常。
protected String determinePrimaryCandidate(Map<String, Object> candidateBeans, Class<?> requiredType) {
String primaryBeanName = null;
for (Map.Entry<String, Object> entry : candidateBeans.entrySet()) {
String candidateBeanName = entry.getKey();
Object beanInstance = entry.getValue();
if (isPrimary(candidateBeanName, beanInstance)) {
if (primaryBeanName != null) {
boolean candidateLocal = containsBeanDefinition(candidateBeanName);
boolean primaryLocal = containsBeanDefinition(primaryBeanName);
if (candidateLocal && primaryLocal) {
throw new NoUniqueBeanDefinitionException(requiredType, candidateBeans.size(),
"more than one 'primary' bean found among candidates: " + candidateBeans.keySet());
}
else if (candidateLocal) {
primaryBeanName = candidateBeanName;
}
}
else {
primaryBeanName = candidateBeanName;
}
}
}
return primaryBeanName;
}
Priority
你也可以在Bean上配置@Priority注解,它有個(gè)int類型的屬性value,可以配置優(yōu)先級(jí)大小。數(shù)字越小的,就被優(yōu)先匹配。同樣的,你也不能把多個(gè)Bean的優(yōu)先級(jí)配置成相同大小的數(shù)值,否則NoUniqueBeanDefinitionException異常照樣出來(lái)找你。
protected String determineHighestPriorityCandidate(Map<String, Object> candidateBeans,
Class<?> requiredType) {
String highestPriorityBeanName = null;
Integer highestPriority = null;
for (Map.Entry<String, Object> entry : candidateBeans.entrySet()) {
String candidateBeanName = entry.getKey();
Object beanInstance = entry.getValue();
Integer candidatePriority = getPriority(beanInstance);
if (candidatePriority != null) {
if (highestPriorityBeanName != null) {
//如果優(yōu)先級(jí)大小相同
if (candidatePriority.equals(highestPriority)) {
throw new NoUniqueBeanDefinitionException(requiredType, candidateBeans.size(),
"Multiple beans found with the same priority ('" + highestPriority + "') " +
"among candidates: " + candidateBeans.keySet());
}
else if (candidatePriority < highestPriority) {
highestPriorityBeanName = candidateBeanName;
highestPriority = candidatePriority;
}
}
else {
highestPriorityBeanName = candidateBeanName;
highestPriority = candidatePriority;
}
}
}
return highestPriorityBeanName;
}
最后,有一點(diǎn)需要注意。Priority的包在javax.annotation.Priority;,如果想使用它還要引入一個(gè)坐標(biāo)。
<dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.2</version> </dependency>
三、總結(jié)
本章節(jié)重點(diǎn)闡述了Spring中的自動(dòng)裝配的幾種策略,又通過(guò)源碼分析了Autowired注解的使用方式。
在Spring3.0之后,有效的自動(dòng)裝配策略分為byType、byName、constructor三種方式。注解Autowired默認(rèn)使用byType來(lái)自動(dòng)裝配,如果存在類型的多個(gè)實(shí)例就嘗試使用byName匹配,如果通過(guò)byName也確定不了,可以通過(guò)Primary和Priority注解來(lái)確定。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
關(guān)于@Transactional事務(wù)嵌套使用方式
Spring框架通過(guò)@Transactional注解來(lái)管理事務(wù),它可以作用于類和方法上,用于聲明事務(wù)的屬性,如傳播行為、隔離級(jí)別、超時(shí)時(shí)間等,Spring事務(wù)是基于AOP實(shí)現(xiàn)的,它在運(yùn)行時(shí)為加了@Transactional注解的方法或類創(chuàng)建代理2024-11-11
AsyncHttpClient KeepAliveStrategy源碼流程解讀
這篇文章主要為大家介紹了AsyncHttpClient KeepAliveStrategy源碼流程解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
SPRINGBOOT讀取PROPERTIES配置文件數(shù)據(jù)過(guò)程詳解
這篇文章主要介紹了SPRINGBOOT讀取PROPERTIES配置文件數(shù)據(jù)過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
微信開(kāi)發(fā)準(zhǔn)備第一步 Maven倉(cāng)庫(kù)管理新建WEB項(xiàng)目
這篇文章主要為大家詳細(xì)介紹了微信開(kāi)發(fā)準(zhǔn)備第一步,Maven倉(cāng)庫(kù)管理新建WEB項(xiàng)目,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
SpringSecurity+OAuth2.0?搭建認(rèn)證中心和資源服務(wù)中心流程分析
OAuth?2.0?主要用于在互聯(lián)網(wǎng)上安全地委托授權(quán),廣泛應(yīng)用于身份驗(yàn)證和授權(quán)場(chǎng)景,這篇文章介紹SpringSecurity+OAuth2.0?搭建認(rèn)證中心和資源服務(wù)中心,感興趣的朋友一起看看吧2024-01-01
Java反射根據(jù)不同方法名動(dòng)態(tài)調(diào)用不同的方法(實(shí)例)
下面小編就為大家?guī)?lái)一篇Java反射根據(jù)不同方法名動(dòng)態(tài)調(diào)用不同的方法(實(shí)例)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-08-08
網(wǎng)易Java程序員兩輪面試 請(qǐng)問(wèn)你能答對(duì)幾個(gè)?
為大家分享網(wǎng)易Java程序員兩輪面試題,考考大家,這些問(wèn)題你能答對(duì)幾個(gè)?2017-11-11
Mybatis-plus如何通過(guò)反射實(shí)現(xiàn)動(dòng)態(tài)排序不同字段功能
這篇文章主要介紹了Mybatis-plus如何通過(guò)反射實(shí)現(xiàn)動(dòng)態(tài)排序不同字段功能,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-02-02

