您的位置:首页 > 编程语言 > Java开发

Spring中的自动装配和Autowired注解的使用

2020-02-13 08:41 441 查看

目录

Spring中的自动装配和Autowired注解的使用

一、自动装配

1、byName

2、byType

3、constructor

4、autodetect

5、默认自动装配

二、Autowired

1、强制性

2、装配策略

3、主和优先级

三、总结

Spring中的自动装配和Autowired注解的使用

一、自动装配

当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注解来确定。

  • 点赞
  • 收藏
  • 分享
  • 文章举报
Tony_313726 发布了20 篇原创文章 · 获赞 4 · 访问量 800 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: