一、自动装配
当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