一、自動裝配
當spring裝配bean屬性時,有時候非常明確,就是需要將某個bean的引用裝配給指定屬性。比如,如果我們的應用上下文中只有一個org.mybatis.spring.sqlsessionfactorybean類型的bean,那么任意一個依賴sqlsessionfactorybean的其他bean就是需要這個bean。畢竟這里只有一個sqlsessionfactorybean的bean。
為了應對這種明確的裝配場景,spring提供了自動裝配(autowiring)。與其顯式的裝配bean屬性,為何不讓spring識別出可以自動裝配的場景。
當涉及到自動裝配bean的依賴關系時,spring有多種處理方式。因此,spring提供了4種自動裝配策略。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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之后已經不再支持。
1、byname
它的意思是,把與bean的屬性具有相同名字的其他bean自動裝配到bean的對應屬性中。聽起來可能比較拗口,我們來看個例子。
首先,在user的bean中有個屬性role myrole,再創建一個role的bean,它的名字如果叫myrole,那么在user中就可以使用byname來自動裝配。
1
2
3
4
5
6
7
|
public class user{ private role myrole; } public class role { private string id; private string name; } |
上面是bean的定義,再看配置文件。
1
2
3
4
5
6
|
<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來自動裝配。那么,如果屬性名稱對應不上呢?
2、bytype
是的,如果不使用屬性名稱來對應,你也可以選擇使用類型來自動裝配。它的意思是,把與bean的屬性具有相同類型的其他bean自動裝配到bean的對應屬性中。
1
2
3
4
5
6
|
<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
它是說,把與bean的構造器入參具有相同類型的其他bean自動裝配到bean構造器的對應入參中。值的注意的是,具有相同類型的其他bean這句話說明它在查找入參的時候,還是通過bean的類型來確定。
構造器中入參的類型為role
1
2
3
4
5
6
7
8
9
|
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
它首先會嘗試使用constructor進行自動裝配,如果失敗再嘗試使用bytype。不過,它在spring3.0之后已經被標記為@deprecated。
5、默認自動裝配
默認情況下,default-autowire屬性被設置為none,標示所有的bean都不使用自動裝配,除非bean上配置了autowire屬性。
如果你需要為所有的bean配置相同的autowire屬性,有個辦法可以簡化這一操作。
在根元素beans上增加屬性default-autowire="bytype"。
1
|
<beans default -autowire= "bytype" > |
spring自動裝配的優點不言而喻。但是事實上,在spring xml配置文件里的自動裝配并不推薦使用,其中筆者認為最大的缺點在于不確定性。或者除非你對整個spring應用中的所有bean的情況了如指掌,不然隨著bean的增多和關系復雜度的上升,情況可能會很糟糕
二、autowired
從spring2.5開始,開始支持使用注解來自動裝配bean的屬性。它允許更細粒度的自動裝配,我們可以選擇性的標注某一個屬性來對其應用自動裝配。
spring支持幾種不同的應用于自動裝配的注解。
- spring自帶的@autowired注解。
- jsr-330的@inject注解。
- jsr-250的@resource注解。
我們今天只重點關注autowired注解,關于它的解析和注入過程,請參考筆者spring源碼系列的文章。spring源碼分析(二)bean的實例化和ioc依賴注入
使用@autowired很簡單,在需要注入的屬性加入注解即可。
1
2
|
@autowired userservice userservice; |
不過,使用它有幾個點需要注意。
1、強制性
默認情況下,它具有強制契約特性,其所標注的屬性必須是可裝配的。如果沒有bean可以裝配到autowired所標注的屬性或參數中,那么你會看到nosuchbeandefinitionexception
的異常信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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。
1
2
|
@autowired (required= false ) userservice userservice; |
2、裝配策略
我記得曾經有個面試題是這樣問的:autowired是按照什么策略來自動裝配的呢?
關于這個問題,不能一概而論,你不能簡單的說按照類型或者按照名稱。但可以確定的一點的是,它默認是按照類型來自動裝配的,即bytype。
默認按照類型裝配
關鍵點findautowirecandidates這個方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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實現類的名稱。
1
2
|
@autowired userservice userserviceimpl2; |
這樣的話,spring會按照byname來進行裝配。首先,如果查到類型的多個實例,spring已經做了判斷。
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
|
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的名稱來匹配。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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。
3、主和優先級
上面我們已經看到了,通過bytype可能會找到多個實例的bean。然后再通過byname來確定一個合適的bean,如果通過名稱也確定不了呢?
還是determineautowirecandidate
這個方法,它還有兩種方式來確定。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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 ; } |
primary
它的作用是看bean上是否包含@primary注解,如果包含就返回。當然了,你不能把多個bean都設置為@primary,不然你會得到nouniquebeandefinitionexception
這個異常。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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注解,它有個int類型的屬性value,可以配置優先級大小。數字越小的,就被優先匹配。同樣的,你也不能把多個bean的優先級配置成相同大小的數值,否則nouniquebeandefinitionexception
異常照樣出來找你。
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
|
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;,如果想使用它還要引入一個坐標。
1
2
3
4
5
|
<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注解來確定。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://juejin.im/post/5c84b5285188257c5b477177