Spring源码系列(⼗⼆)——Spring⾃动注⼊原理及源码分析
⽂章⽬录
⼀、@Autowired注解引出的问题
@Autowired这个注解相信使⽤Spring开发的⼈应该都不陌⽣了,但不知道⼤家有没有留意,在我们使⽤IDEA写代码的时候,经常会发现@Autowired注解下⾯是有⼩黄线的,把⿏标悬停在上⾯,可以看到下图所⽰的警告信息:
那为什么IDEA会给出Field injection is not recommended这样的警告呢?
下⾯带着这样的问题,⼀起来了解下Spring中三种注⼊⽅式以及它们之间在各⽅⾯的优劣。
⼆、Java中的属性赋值
在介绍Spring三种注⼊⽅式之前,我们先来思考⼀下,在Java中,我们是如何给⼀个对象的属性赋值的?是不是有以下三种⽅式:反射
构造函数
set⽅法
下⾯来看个例⼦:
public class User {
private String userName;
private String passWord;
public User(){}
public User(String userName, String passWord){
this.userName = userName;
this.passWord = passWord;
}
public String getUserName(){
return userName;
}
public void setUserName(String userName){
this.userName = userName;
}
public String getPassWord(){
return passWord;
}
public void setPassWord(String passWord){
this.passWord = passWord;
}
@Override
public String toString(){
return"User{"+
"userName='"+ userName +'\''+
", passWord='"+ passWord +'\''+
'}';
}
}
测试⽅法:
public static void main(String[] args)throws Exception {
System.out.println("---------------为属性赋值,⽅式⼀:反射---------------");
User user1 =new User();
Field userName = Class().getDeclaredField("userName");
Field passWord = Class().getDeclaredField("passWord");
userName.setAccessible(true);
userName.set(user1,"Tom");
passWord.setAccessible(true);
passWord.set(user1,"Tom1234");
System.out.UserName()+" : "+ PassWord());
System.out.println("---------------为属性赋值,⽅式⼆:构造⽅法---------------"); User user2 =new User("Jack","Jack1234");
System.out.UserName()+" : "+ PassWord());
resource和autowired注解的区别System.out.println("---------------为属性赋值,⽅式三:set⽅法---------------"); User user3 =new User();
user3.setUserName("Rose");
user3.setPassWord("Rose1234");
System.out.UserName()+" : "+ PassWord());
}
三、Spring中的三种依赖注⼊⽅式
在了解Java的三种属性赋值⽅法后,我们来探究下Spring中的三种依赖注⼊⽅式,其实底层就是使⽤Java属性赋值的这三种⽅式。1. Field Injection
@Autowired注解的⼀⼤使⽤场景就是Field Injection,这种注⼊⽅式通过Java的反射机制实现,所以private的成员也可以被注⼊具体的对象。
具体形式如下:
@Controller
public class UserController {
@Autowired
private UserService userService;
}
2. Constructor Injection
Constructor Injection是构造器注⼊,是我们⽇常最为推荐的⼀种使⽤⽅式,但发现在⽇常写代码中,很少看到有⼈这么写!
具体形式如下:
@Controller
public class UserController {
private final UserService userService;
public UserController(UserService userService){
this.userService = userService;
}
}
这种注⼊⽅式很直接,通过对象构建的时候建⽴关系,所以这种⽅式对对象创建的顺序会有要求,当然Spring会为你搞定这样的先后顺序,除⾮你出现循环依赖,然后就会抛出异常
注意:此种⽅式不⽀持循环依赖
3. Setter Injection
Setter Injection也会⽤到@Autowired注解,但使⽤⽅式与Field Injection有所不同,Field Injection是⽤在成员变量上,⽽Setter Injection的时候,是⽤在成员变量的Setter函数上。
具体形式如下:
@Controller
public class UserController {
private UserService userService;
@Autowired
public void setUserService(UserService userService){
this.userService = userService;
}
}
这种注⼊⽅式也很好理解,就是通过调⽤成员变量的set⽅法来注⼊想要使⽤的依赖对象
上⾯三种注⼊⽅式,你经常⽤哪⼀种呢?
可以在任意⼀个⽅法上添加这个@Autowired注解,Spring会在项⽬启动的过程中,⾃动调⽤⼀次加了@Autowired注解的⽅法,我们可以在该⽅法做⼀些初始化的⼯作
也就是说不⼀定需要set⽅法,只需要提供⼀个⽅法,然后在上⾯加上@Autowired,都能实现属性依赖注⼊。
4. 三种依赖注⼊的对⽐
在知道了Spring提供的三种依赖注⼊⽅式之后,我们继续回到上⾯的问题:IDEA为什么不推荐使⽤Field Injection呢?我们可以从多个开发测试的考察⾓度来对⽐⼀下它们之间的优劣:
1. 可靠性
从对象构建过程和使⽤过程,看对象在各阶段的使⽤是否可靠来评判:
Field Injection:不可靠
Constructor Injection:可靠
Setter Injection:不可靠
Field Injection和Setter Injection对象状态是不连续的,所以不可靠。⽽构造函数有严格的构建顺序和
不可变性,⼀旦构建就可⽤,且不会被更改。
2. 可维护性
主要从更容易阅读、分析依赖关系的⾓度来评判:
Field Injection:差
Constructor Injection:好
Setter Injection:差
还是由于依赖关系的明确,从构造函数中可以显现的分析出依赖关系,对于我们如何去读懂关系和维护关系更友好。
3. 可测试性
当在复杂依赖关系的情况下,考察程序是否更容易编写单元测试来评判
Field Injection:差
Constructor Injection:好
Setter Injection:好
Constructor Injection和Setter Injection的⽅式更容易Mock和注⼊对象,所以更容易实现单元测试(从调试代码的⾓度)。
4. 灵活性
主要根据开发实现时候的编码灵活性来判断:
Field Injection:很灵活
Constructor Injection:不灵活
Setter Injection:很灵活
由于Constructor Injection对Bean的依赖关系设计有严格的顺序要求,所以这种注⼊⽅式不太灵活。相反Field Injection和Setter Injection就⾮常灵活,但也因为灵活带来了局⾯的混乱,也是⼀把双刃剑
5. 循环关系的检测
对于Bean之间是否存在循环依赖关系的检测能⼒:
Field Injection:不检测
Constructor Injection:⾃动检测
Setter Injection:不检测
6. 性能表现
不同的注⼊⽅式,对性能的影响
Field Injection:启动快
Constructor Injection:启动慢
Setter Injection:启动快
主要影响就是启动时间,由于Constructor Injection有严格的顺序要求,所以会拉长启动时间。
所以,综合上⾯各⽅⾯的⽐较,可以获得如下表格:
注⼊⽅式可靠性可维护性可测试性灵活性循环依赖检查性能影响Field Injection不可靠低差很灵活不检
测启动快Constructor Injection可靠⾼好不灵活⾃动检测启动慢Setter Injection不可靠低好很灵活不检测启动快
Constructor Injection在很多⽅⾯都是优于其他两种⽅式的,所以Constructor Injection通常都是⾸选⽅案,⽽Setter Injection⽐起Field Injection来说,⼤部分都⼀样,但因为可测试性更好,所以当你要⽤@Autowired的时候,推荐使⽤Setter Injection的⽅式,这样IDEA也不会给出警告了。同时,也侧⾯反映了,可测试性的重要地位啊!
7. 总结
依赖注⼊的使⽤上,Constructor Injection是⾸选,使⽤@Autowired注解的时候,要使⽤Setter Injection⽅式,这样代码更容易编写单元测试。
四、Spring⾃动装配
1. 装配模式
上⾯讲解了如何将值注⼊到属性中,那么Spring容器如何去到这个属性值呢?
⾃动装配嘛,说的就是Spring⾃⼰去到对应的属性,然后完成装配。在Spring中,定义了五种模式:
no(default):这是Spring框架的默认设置,在该设置下⾃动装配是关闭的,开发者需要⾃⾏在bean定义中⽤标签明确的设置依赖关系
byName:该选项可以根据bean名称设置依赖关系,当向⼀个bean中⾃动装配⼀个属性时,容器将根据bean的名称⾃动在在配置⽂件中查询⼀个匹配的bean,如果到的话,就装配这个属性,如果没到的话就报错
byType:该选项可以根据bean类型设置依赖关系,当向⼀个bean中⾃动装配⼀个属性时,容器将根据bean的类型⾃动在在配置⽂件中查询⼀个匹配的bean,如果到的话,就装配这个属性,如果没到的话就报错
constructor:构造器的⾃动装配和byType模式类似,但是仅仅适⽤于与有构造器相同参数的bean,如果在容器中没有到与构造器参数类型⼀致的bean,那么将会抛出异常
autodetect:该模式⾃动探测使⽤构造器⾃动装配或者byType⾃动装配。⾸先,⾸先会尝试合适的带参数的构造器,如果到的话就是⽤构造器⾃动装配,如果在bean内部没有到相应的构造器或者是⽆参构造器,容器就会⾃动选择byTpe的⾃动装配⽅式。
如果是使⽤xml的⽅式来配置Spring,那么可以在<beans>标签中添加全局装配模式:default-autowire
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论