當Spring裝配Bean屬性時,有時候很是明確,就是須要將某個Bean的引用裝配給指定屬性。好比,若是咱們的應用上下文中只有一個org.mybatis.spring.SqlSessionFactoryBean
類型的Bean,那麼任意一個依賴SqlSessionFactoryBean
的其餘Bean就是須要這個Bean。畢竟這裏只有一個SqlSessionFactoryBean
的Bean。java
爲了應對這種明確的裝配場景,Spring提供了自動裝配(autowiring)。與其顯式的裝配Bean屬性,爲什麼不讓Spring識別出能夠自動裝配的場景。面試
當涉及到自動裝配Bean的依賴關係時,Spring有多種處理方式。所以,Spring提供了4種自動裝配策略。spring
public interface AutowireCapableBeanFactory{
//無需自動裝配
int AUTOWIRE_NO = 0;
//按名稱自動裝配bean屬性
int AUTOWIRE_BY_NAME = 1;
//按類型自動裝配bean屬性
int AUTOWIRE_BY_TYPE = 2;
//按構造器自動裝配
int AUTOWIRE_CONSTRUCTOR = 3;
//過期方法,Spring3.0以後再也不支持
@Deprecated
int AUTOWIRE_AUTODETECT = 4;
}
複製代碼
Spring在AutowireCapableBeanFactory
接口中定義了這幾種策略。其中,AUTOWIRE_AUTODETECT
被標記爲過期方法,在Spring3.0以後已經再也不支持。api
它的意思是,把與Bean的屬性具備相同名字的其餘Bean自動裝配到Bean的對應屬性中。聽起來可能比較拗口,咱們來看個例子。bash
首先,在User的Bean中有個屬性Role myRole
,再建立一個Role的Bean,它的名字若是叫myRole,那麼在User中就可使用byName來自動裝配。mybatis
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的名稱能夠對應,那麼在user的Bean中就可使用byName來自動裝配。那麼,若是屬性名稱對應不上呢?ui
是的,若是不使用屬性名稱來對應,你也能夠選擇使用類型來自動裝配。它的意思是,把與Bean的屬性具備相同類型的其餘Bean自動裝配到Bean的對應屬性中。this
<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均可以省去。spa
它是說,把與Bean的構造器入參具備相同類型的其餘Bean自動裝配到Bean構造器的對應入參中。值的注意的是,具備相同類型的其餘Bean這句話說明它在查找入參的時候,仍是經過Bean的類型來肯定。
構造器中入參的類型爲Role
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>
複製代碼
它首先會嘗試使用constructor進行自動裝配,若是失敗再嘗試使用byType。不過,它在Spring3.0以後已經被標記爲@Deprecated
。
默認狀況下,default-autowire屬性被設置爲none,標示全部的Bean都不使用自動裝配,除非Bean上配置了autowire屬性。 若是你須要爲全部的Bean配置相同的autowire屬性,有個辦法能夠簡化這一操做。 在根元素Beans上增長屬性default-autowire="byType"
。
<beans default-autowire="byType">
複製代碼
Spring自動裝配的優勢不言而喻。可是事實上,在Spring XML配置文件裏的自動裝配並不推薦使用,其中筆者認爲最大的缺點在於不肯定性。或者除非你對整個Spring應用中的全部Bean的狀況瞭如指掌,否則隨着Bean的增多和關係複雜度的上升,狀況可能會很糟糕。
從Spring2.5開始,開始支持使用註解來自動裝配Bean的屬性。它容許更細粒度的自動裝配,咱們能夠選擇性的標註某一個屬性來對其應用自動裝配。
Spring支持幾種不一樣的應用於自動裝配的註解。
Spring自帶的@Autowired註解。
JSR-330的@Inject註解。
JSR-250的@Resource註解。
咱們今天只重點關注Autowired註解,關於它的解析和注入過程,請參考筆者Spring源碼系列的文章。Spring源碼分析(二)bean的實例化和IOC依賴注入
使用@Autowired很簡單,在須要注入的屬性加入註解便可。
@Autowired
UserService userService;
複製代碼
不過,使用它有幾個點須要注意。
默認狀況下,它具備強制契約特性,其所標註的屬性必須是可裝配的。若是沒有Bean能夠裝配到Autowired所標註的屬性或參數中,那麼你會看到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集合爲空沒關係,關鍵isRequired
條件不能成立,那麼,若是咱們不肯定屬性是否能夠裝配,能夠這樣來使用Autowired。
@Autowired(required=false)
UserService userService;
複製代碼
我記得曾經有個面試題是這樣問的:Autowired是按照什麼策略來自動裝配的呢?
關於這個問題,不能一律而論,你不能簡單的說按照類型或者按照名稱。但能夠肯定的一點的是,它默認是按照類型來自動裝配的,即byType。
關鍵點findAutowireCandidates
這個方法。
protected Map<String, Object> findAutowireCandidates(
String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
//獲取給定類型的全部bean名稱,裏面實際循環全部的beanName,獲取它的實例
//再經過isTypeMatch方法來肯定
String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this, requiredType, true, descriptor.isEager());
Map<String, Object> result = new LinkedHashMap<String, Object>(candidateNames.length);
//根據返回的beanName,獲取其實例返回
for (String candidateName : candidateNames) {
if (!isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, descriptor)) {
result.put(candidateName, getBean(candidateName));
}
}
return result;
}
複製代碼
能夠看到它返回的是一個列表,那麼就代表,按照類型匹配可能會查詢到多個實例。到底應該裝配哪一個實例呢?我看有的文章裏說,能夠加註解以此規避。好比@qulifier、@Primary
等,實際還有個簡單的辦法。
好比,按照UserService接口類型來裝配它的實現類。UserService接口有多個實現類,分爲UserServiceImpl、UserServiceImpl2
。那麼咱們在注入的時候,就能夠把屬性名稱定義爲Bean實現類的名稱。
@Autowired
UserService UserServiceImpl2;
複製代碼
這樣的話,Spring會按照byName來進行裝配。首先,若是查到類型的多個實例,Spring已經作了判斷。
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實例大於1個
if (matchingBeans.size() > 1) {
//找到最合適的那個,若是沒有合適的。。也拋出異常
String primaryBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (primaryBeanName == null) {
throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet());
}
if (autowiredBeanNames != null) {
autowiredBeanNames.add(primaryBeanName);
}
return matchingBeans.get(primaryBeanName);
}
}
複製代碼
能夠看出,若是查到多個實例,determineAutowireCandidate
方法就是關鍵。它來肯定一個合適的Bean返回。其中一部分就是按照Bean的名稱來匹配。
protected String determineAutowireCandidate(Map<String, Object> candidateBeans,
DependencyDescriptor descriptor) {
//循環拿到的Bean集合
for (Map.Entry<String, Object> entry : candidateBeans.entrySet()) {
String candidateBeanName = entry.getKey();
Object beanInstance = entry.getValue();
//經過matchesBeanName方法來肯定bean集合中的名稱是否與屬性的名稱相同
if (matchesBeanName(candidateBeanName, descriptor.getDependencyName())) {
return candidateBeanName;
}
}
return null;
}
複製代碼
最後咱們回到問題上,獲得的答案就是:@Autowired默認使用byType來裝配屬性,若是匹配到類型的多個實例,再經過byName來肯定Bean。
上面咱們已經看到了,經過byType可能會找到多個實例的Bean。而後再經過byName來肯定一個合適的Bean,若是經過名稱也肯定不了呢? 仍是determineAutowireCandidate
這個方法,它還有兩種方式來肯定。
protected String determineAutowireCandidate(Map<String, Object> candidateBeans,
DependencyDescriptor descriptor) {
Class<?> requiredType = descriptor.getDependencyType();
//經過@Primary註解來標識Bean
String primaryCandidate = determinePrimaryCandidate(candidateBeans, requiredType);
if (primaryCandidate != null) {
return primaryCandidate;
}
//經過@Priority(value = 0)註解來標識Bean value爲優先級大小
String priorityCandidate = determineHighestPriorityCandidate(candidateBeans, requiredType);
if (priorityCandidate != null) {
return priorityCandidate;
}
return null;
}
複製代碼
它的做用是看Bean上是否包含@Primary註解,若是包含就返回。固然了,你不能把多個Bean都設置爲@Primary,否則你會獲得NoUniqueBeanDefinitionException
這個異常。
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;
}
複製代碼
你也能夠在Bean上配置@Priority註解,它有個int類型的屬性value,能夠配置優先級大小。數字越小的,就被優先匹配。一樣的,你也不能把多個Bean的優先級配置成相同大小的數值,不然NoUniqueBeanDefinitionException
異常照樣出來找你。
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) {
//若是優先級大小相同
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;
}
複製代碼
最後,有一點須要注意。Priority的包在javax.annotation.Priority;
,若是想使用它還要引入一個座標。
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.2</version>
</dependency>
複製代碼
本章節重點闡述了Spring中的自動裝配的幾種策略,又經過源碼分析了Autowired註解的使用方式。 在Spring3.0以後,有效的自動裝配策略分爲byType、byName、constructor
三種方式。註解Autowired默認使用byType來自動裝配,若是存在類型的多個實例就嘗試使用byName匹配,若是經過byName也肯定不了,能夠經過Primary和Priority註解來肯定。