- 一、@Autowired注解引出的问题
- 二、Java中的属性赋值
- 三、Spring中的三种依赖注入方式
- 1. Field Injection
- 2. Constructor Injection
- 3. Setter Injection
- 4. 三种依赖注入的对比
- 1. 可靠性
- 2. 可维护性
- 3. 可测试性
- 4. 灵活性
- 5. 循环关系的检测
- 6. 性能表现
- 7. 总结
- 四、Spring自动装配
- 1. 装配模式
- 2. @Autowired装配
- 3. @Resource装配
@Autowired
这个注解相信使用Spring
开发的人应该都不陌生了,但不知道大家有没有留意,在我们使用IDEA
写代码的时候,经常会发现@Autowired
注解下面是有小黄线的,把鼠标悬停在上面,可以看到下图所示的警告信息:
那为什么
IDEA
会给出Field injection is not recommended
这样的警告呢?
下面带着这样的问题,一起来了解下Spring
中三种注入方式以及它们之间在各方面的优劣。
在介绍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 = user1.getClass().getDeclaredField("userName");
Field passWord = user1.getClass().getDeclaredField("passWord");
userName.setAccessible(true);
userName.set(user1, "Tom");
passWord.setAccessible(true);
passWord.set(user1, "Tom1234");
System.out.println(user1.getUserName() + " : "+ user1.getPassWord());
System.out.println("---------------为属性赋值,方式二:构造方法---------------");
User user2 = new User("Jack","Jack1234");
System.out.println(user2.getUserName() + " : "+ user2.getPassWord());
System.out.println("---------------为属性赋值,方式三:set方法---------------");
User user3 = new User();
user3.setUserName("Rose");
user3.setPassWord("Rose1234");
System.out.println(user3.getUserName() + " : "+ user3.getPassWord());
}
在了解Java
的三种属性赋值方法后,我们来探究下Spring
中的三种依赖注入方式,其实底层就是使用Java
属性赋值的这三种方式。
@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 InjectionSetter 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
,都能实现属性依赖注入。
在知道了Spring
提供的三种依赖注入方式之后,我们继续回到上面的问题:IDEA
为什么不推荐使用Field Injection
呢?我们可以从多个开发测试的考察角度来对比一下它们之间的优劣:
从对象构建过程和使用过程,看对象在各阶段的使用是否可靠来评判:
Field Injection
:不可靠Constructor Injection
:可靠Setter Injection
:不可靠
Field Injection
和Setter Injection
对象状态是不连续的,所以不可靠。而构造函数有严格的构建顺序和不可变性,一旦构建就可用,且不会被更改。
主要从更容易阅读、分析依赖关系的角度来评判:
-
Field Injection
:差 -
Constructor Injection
:好 -
Setter Injection
:差
还是由于依赖关系的明确,从构造函数中可以显现的分析出依赖关系,对于我们如何去读懂关系和维护关系更友好。
3. 可测试性当在复杂依赖关系的情况下,考察程序是否更容易编写单元测试来评判
Field Injection
:差Constructor Injection
:好Setter Injection
:好
Constructor Injection
和Setter Injection
的方式更容易Mock和注入对象,所以更容易实现单元测试(从调试代码的角度)。
主要根据开发实现时候的编码灵活性来判断:
Field Injection
:很灵活Constructor Injection
:不灵活Setter Injection
:很灵活
由于Constructor Injection
对Bean
的依赖关系设计有严格的顺序要求,所以这种注入方式不太灵活。相反Field Injection
和Setter Injection
就非常灵活,但也因为灵活带来了局面的混乱,也是一把双刃剑
对于Bean
之间是否存在循环依赖关系的检测能力:
-
Field Injection
:不检测 -
Constructor Injection
:自动检测 -
Setter Injection
:不检测
不同的注入方式,对性能的影响
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
也不会给出警告了。同时,也侧面反映了,可测试性的重要地位啊!
依赖注入的使用上,Constructor Injection
是首选,使用@Autowired
注解的时候,要使用Setter Injection
方式,这样代码更容易编写单元测试。
上面讲解了如何将值注入到属性中,那么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
,那么可以在标签中添加全局装配模式:
default-autowire
如果是使用非xml
方式来配置Spring
,那么就无法设置这个自动装配的模式,这样就默认是no模式
,于是Spring
提供了注解的方式协助自动装配,在no模式
下只要加上例如@Autowired
、@Resource
注解也可以完成自动装配。
Spring的自动装配永远都是开启的,只是默认模式是no罢了,在no模式下只要加上例如@Autowired、@Resource注解也可以完成自动装配
一般意义上来说,我们理解的给某个属性上加上@Autowired
就会根据类型ByType
装配,加上@Resource
就是ByName
,其实并不是。那么什么是自动装配呢,只要一个类里面有属性,就会被自动装配,只不过这个装配的规则为Autowired_No
,也就是说不进行装配的动作。
先说结论,然后验证:
@Autowired默认是根据byType来实现自动装配,如果byType找到多个,再会根据byName去找。
源码分析:有一个接口HomeDao
,然后有两个实现类HomeADao
和HomeBDao
,以及一个HomeService
去依赖HomeDao
@Service
public class HomeService {
@Autowired
HomeDao dao;
public void print(){
dao.home();
}
}
public interface HomeDao {
void home();
}
@Component("dao") // 此Bean的名字为dao,因为要演示如果byType找到多个,那么就根据byName装配
public class HomeADao implements HomeDao{
@Override
public void home() {
System.out.println("home A ...");
}
}
@Component("homeBDao")
public class HomeBDao implements HomeDao{
@Override
public void home() {
System.out.println("home B ...");
}
}
如果@Autowired
是按byType
进行自动装配,那么上面HomeService
在装配的时候会拿到两个HomeDao
类型,所以程序应该会报错,但结果并不是这样的,而是会把name
为dao
的Bean
注入进去,下面来分析下源码。
我们知道,自动装配发生在属性填充阶段,即在polulateBean()
方法后,会去调用BeanPostProcessor
后置处理器来实现自动装配,@Autowired
用到的后置处理器是AutowiredAnnotationBeanPostProcessor
。
下面来看一下,Spring
中方法的调用链:
inject:574, AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement (org.springframework.beans.factory.annotation)
inject:91, InjectionMetadata (org.springframework.beans.factory.annotation)
postProcessPropertyValues:362, AutowiredAnnotationBeanPostProcessor (org.springframework.beans.factory.annotation)
populateBean:1384, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:589, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:499, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:350, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, 878274034 (org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$7)
getSingleton:237, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:348, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:199, AbstractBeanFactory (org.springframework.beans.factory.support)
preInstantiateSingletons:767, DefaultListableBeanFactory (org.springframework.beans.factory.support)
finishBeanFactoryInitialization:906, AbstractApplicationContext (org.springframework.context.support)
refresh:562, AbstractApplicationContext (org.springframework.context.support)
main:12, Test (com.scorpios.test)
AutowiredAnnotationBeanPostProcessor
类中的postProcessPropertyValues()
方法会进入到下面这个inject()
方法:
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs){
Collection checkedElements = this.checkedElements;
Collection elementsToIterate =
(checkedElements != null ? checkedElements : this.injectedElements);
if (!elementsToIterate.isEmpty()) {
for (InjectedElement element : elementsToIterate) {
// 参数target就是homeService,beanName则是homeService
element.inject(target, beanName, pvs);
}
}
}
此处的bean
就是HomeService
,beanName
为homeService
,而此处的依赖Field
就是HomeDao
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs){
// 依赖就是 IndexDao dao;
Field field = (Field) this.member;
Object value; //赋值变量
if (this.cached) {
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
} else {
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass());
Set autowiredBeanNames = new LinkedHashSet(1);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
try {
// value获取到值
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
// 部分代码略
}
if (value != null) {
ReflectionUtils.makeAccessible(field);
// 反射注入字段值value给bean
field.set(bean, value);
}
}
在doResolveDependency()
方法里,会根据HomeDao
类型去Spring
容器中拿到所有Bean
的name
,然后!!!!关键来了。
此方法在@Resource
源码分析时也会用到!!!!
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) {
InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
try {
Object shortcut = descriptor.resolveShortcut(this);
if (shortcut != null) {
return shortcut;
}
// matchingBeans非常重要的一个变量,在此方法中,会根据类型去拿到所有Bean的name
Map matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (matchingBeans.isEmpty()) {
if (isRequired(descriptor)) {
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
}
return null;
}
// 要创建的bean名字
String autowiredBeanName;
// 创建返回对象变量
Object instanceCandidate;
// 关键的判断!!!!!很重要!!!!
if (matchingBeans.size() > 1) {
// 获取要注入的变量名字
autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (autowiredBeanName == null) {
if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
return descriptor.resolveNotUnique(type, matchingBeans);
} else {
return null;
}
}
instanceCandidate = matchingBeans.get(autowiredBeanName);
} else {
// We have exactly one match.
// 如果只匹配到一个,赋值autowiredBeanName和instanceCandidate
Map.Entry entry = matchingBeans.entrySet().iterator().next();
autowiredBeanName = entry.getKey();
instanceCandidate = entry.getValue();
}
if (autowiredBeanNames != null) {
autowiredBeanNames.add(autowiredBeanName);
}
if (instanceCandidate instanceof Class) {
instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
}
// 传递给result
Object result = instanceCandidate;
if (result instanceof NullBean) {
if (isRequired(descriptor)) {
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
}
result = null;
}
if (!ClassUtils.isAssignableValue(type, result)) {
throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
}
// 返回出去
return result;
} finally {
ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
}
}
判断如果if (matchingBeans.size() > 1)
发现要注入的接口有多个实例,再去看看有没有Bean
的name
与需要注入的name
是否一致,如果名字可以直接拿到,那么就从matchingBeans
中直接拿出来,返回出去
如果matchingBeans
只有1个值就直接返回出去了
一句话总结,再来一遍:@Autowired默认是根据byType来实现自动装配,如果byType找到多个,再会根据byName去找,而不是直接报错。
3. @Resource装配先说结论,然后验证:
@Resource首先会通过byName的方式进行注入,如果失败了则进行byType的方式进行注入
源码分析:进行下调整,用@Resource
注解,然后只留一个HomeDao
的实现,且Bean
的name
为dao
@Service
public class HomeService {
@Resource
HomeDao dao;
public void print(){
dao.home();
}
}
public interface HomeDao {
void home();
}
@Component("dao")
public class HomeADao implements HomeDao{
@Override
public void home() {
System.out.println("home A ...");
}
}
通过追踪@Resource
的流程,知道@Resource
用到的后置处理器是CommonAnnotationBeanPostProcessor
,而不是@Autowired
使用的AutowiredAnnotationBeanPostProcessor
。
但是它们前面的流程还是一致的,都是从赋值用的populateBean()
方法进入相应的后置处理器的postProcessPropertyValues()
方法,同样看下,方法的调用链。
postProcessPropertyValues:318, CommonAnnotationBeanPostProcessor (org.springframework.context.annotation)
populateBean:1384, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:589, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:499, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:350, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, 878274034 (org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$7)
getSingleton:237, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:348, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:199, AbstractBeanFactory (org.springframework.beans.factory.support)
preInstantiateSingletons:767, DefaultListableBeanFactory (org.springframework.beans.factory.support)
finishBeanFactoryInitialization:906, AbstractApplicationContext (org.springframework.context.support)
refresh:562, AbstractApplicationContext (org.springframework.context.support)
main:12, Test (com.scorpios.test)
CommonAnnotationBeanPostProcessor
类中的postProcessPropertyValues()
方法也会进入到下面这个inject()
方法:
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) {
Collection checkedElements = this.checkedElements;
Collection elementsToIterate =
(checkedElements != null ? checkedElements : this.injectedElements);
if (!elementsToIterate.isEmpty()) {
for (InjectedElement element : elementsToIterate) {
// 参数target就是homeService,beanName则是homeService
element.inject(target, beanName, pvs);
}
}
}
到目前为止可以看到虽然@Reource
和@Autowired
使用的后置处理器是不一样的,但是其代码是没有什么变化的。真正变化的地方就是在element.inject(target, beanName, pvs)
这里。
回想一下@Autowired
使用的是AutowiredAnnotationBeanPostProcessor
内部类的方法AutowiredFieldElement#inject()
。但是@Resource
使用的则是InjectionMetadata
内部类的方法InjectedElement#inject()
protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs) {
if (this.isField) {
// 拿到字段
Field field = (Field) this.member;
ReflectionUtils.makeAccessible(field);
// 注入
field.set(target, getResourceToInject(target, requestingBeanName));
} else {
// 其他代码略
}
}
protected Object getResourceToInject(Object target, @Nullable String requestingBeanName){
return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :
getResource(this, requestingBeanName));
}
进入以后,由于不需要设置懒加载,到getResource(this, requestingBeanName)
方法里面。
protected Object getResource(LookupElement element, @Nullable String requestingBeanName)
throws NoSuchBeanDefinitionException {
if (StringUtils.hasLength(element.mappedName)) {
return this.jndiFactory.getBean(element.mappedName, element.lookupType);
}
if (this.alwaysUseJndiLookup) {
return this.jndiFactory.getBean(element.name, element.lookupType);
}
if (this.resourceFactory == null) {
// 抛异常略
}
return autowireResource(this.resourceFactory, element, requestingBeanName);
}
碰到几个if
语句,看条件的内容,显然是Spring容器自己的类,直接到return
语句的方法autowireResource()
方法里。
protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName) {
Object resource;
Set autowiredBeanNames;
String name = element.name;
// 这个 !factory.containsBean(name) 判断很重要啊!!!!!
if (this.fallbackToDefaultTypeMatch && element.isDefaultName &&
factory instanceof AutowireCapableBeanFactory && !factory.containsBean(name)) {
autowiredBeanNames = new LinkedHashSet();
resource = ((AutowireCapableBeanFactory) factory).resolveDependency(
element.getDependencyDescriptor(), requestingBeanName, autowiredBeanNames, null);
// 抛异常略
} else {
// 根据name和类型从容器中获取Bean
resource = factory.getBean(name, element.lookupType);
autowiredBeanNames = Collections.singleton(name);
}
if (factory instanceof ConfigurableBeanFactory) {
ConfigurableBeanFactory beanFactory = (ConfigurableBeanFactory) factory;
for (String autowiredBeanName : autowiredBeanNames) {
if (requestingBeanName != null && beanFactory.containsBean(autowiredBeanName)) {
beanFactory.registerDependentBean(autowiredBeanName, requestingBeanName);
}
}
}
return resource;
}
这里重点来理解一下这个autowireResource()
方法:if
条件判断,第一个条件如果没有特别设置,默认值就是true
,第二个条件是否符合默认的命名,显然也是ture
,第三个条件判断是否是自动注入工厂的类型,很明显这里的factory
属于这个类型
第四个条件,Spring
容器中是否包含指定name
的Bean
!!!
如果factory.containsBean(name)
返回true
,表示Spring
容器中有指定name
的Bean
,此处条件是取反,则会进入else
条件,则直接通过getBean()
方法去拿
如果factory.containsBean(name)
返回false
,表示Spring
容器中没有指定name
的Bean
,此处条件是取反,则会进入if
条件,则会调用DefaultListableBeanFactory#resolveDependency()
,此方法会调用doResolveDependency()
方法,这个方法在上面讲过啦,和@Autowired
进入的地方一样,则会根据类型去容器中拿Bean
,然后进行注入
一句话总结,再来一遍:
@Resource首先会通过byName的方式进行注入,如果失败了则进行byType的方式进行注入