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 = 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());
}

在这里插入图片描述

三、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 InjectionSetter Injection对象状态是不连续的,所以不可靠。而构造函数有严格的构建顺序和不可变性,一旦构建就可用,且不会被更改。

2. 可维护性

主要从更容易阅读、分析依赖关系的角度来评判:

  • Field Injection:差

  • Constructor Injection:好

  • Setter Injection:差

还是由于依赖关系的明确,从构造函数中可以显现的分析出依赖关系,对于我们如何去读懂关系和维护关系更友好。

3. 可测试性

当在复杂依赖关系的情况下,考察程序是否更容易编写单元测试来评判

  • Field Injection:差
  • Constructor Injection:好
  • Setter Injection:好

Constructor InjectionSetter Injection的方式更容易Mock和注入对象,所以更容易实现单元测试(从调试代码的角度)。

4. 灵活性

主要根据开发实现时候的编码灵活性来判断:

  • Field Injection:很灵活
  • Constructor Injection:不灵活
  • Setter Injection:很灵活

由于Constructor InjectionBean的依赖关系设计有严格的顺序要求,所以这种注入方式不太灵活。相反Field InjectionSetter 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

如果是使用非xml方式来配置Spring,那么就无法设置这个自动装配的模式,这样就默认是no模式,于是Spring提供了注解的方式协助自动装配,no模式下只要加上例如@Autowired@Resource注解也可以完成自动装配。

Spring的自动装配永远都是开启的,只是默认模式是no罢了,在no模式下只要加上例如@Autowired、@Resource注解也可以完成自动装配

一般意义上来说,我们理解的给某个属性上加上@Autowired就会根据类型ByType装配,加上@Resource就是ByName,其实并不是。那么什么是自动装配呢,只要一个类里面有属性,就会被自动装配,只不过这个装配的规则为Autowired_No,也就是说不进行装配的动作。

2. @Autowired装配

先说结论,然后验证:

@Autowired默认是根据byType来实现自动装配,如果byType找到多个,再会根据byName去找。

源码分析:有一个接口HomeDao,然后有两个实现类HomeADaoHomeBDao,以及一个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类型,所以程序应该会报错,但结果并不是这样的,而是会把namedaoBean注入进去,下面来分析下源码。

我们知道,自动装配发生在属性填充阶段,即在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<InjectedElement> checkedElements = this.checkedElements;
    Collection<InjectedElement> elementsToIterate =
        (checkedElements != null ? checkedElements : this.injectedElements);
    if (!elementsToIterate.isEmpty()) {
        for (InjectedElement element : elementsToIterate) {
            // 参数target就是homeService,beanName则是homeService
            element.inject(target, beanName, pvs);
        }
    }
}

在这里插入图片描述

此处的bean就是HomeServicebeanNamehomeService,而此处的依赖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<String> 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容器中拿到所有Beanname,然后!!!!关键来了。

此方法在@Resource源码分析时也会用到!!!!

public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
		@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) {

	InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
	try {
		Object shortcut = descriptor.resolveShortcut(this);
		if (shortcut != null) {
			return shortcut;
		}

		// matchingBeans非常重要的一个变量,在此方法中,会根据类型去拿到所有Bean的name
		Map<String, Object> 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<String, Object> 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)发现要注入的接口有多个实例,再去看看有没有Beanname与需要注入的name是否一致,如果名字可以直接拿到,那么就从matchingBeans中直接拿出来,返回出去

如果matchingBeans只有1个值就直接返回出去了

在这里插入图片描述

一句话总结,再来一遍:@Autowired默认是根据byType来实现自动装配,如果byType找到多个,再会根据byName去找,而不是直接报错。

3. @Resource装配

先说结论,然后验证:

@Resource首先会通过byName的方式进行注入,如果失败了则进行byType的方式进行注入

源码分析:进行下调整,用@Resource注解,然后只留一个HomeDao的实现,且Beannamedao

@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<InjectedElement> checkedElements = this.checkedElements;
    Collection<InjectedElement> 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<String> 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容器中是否包含指定nameBean!!!

如果factory.containsBean(name)返回true,表示Spring容器中有指定nameBean,此处条件是取反,则会进入else条件,则直接通过getBean()方法去拿

如果factory.containsBean(name)返回false,表示Spring容器中没有指定nameBean,此处条件是取反,则会进入if条件,则会调用DefaultListableBeanFactory#resolveDependency(),此方法会调用doResolveDependency()方法,这个方法在上面讲过啦,和@Autowired进入的地方一样,则会根据类型去容器中拿Bean,然后进行注入

一句话总结,再来一遍:

@Resource首先会通过byName的方式进行注入,如果失败了则进行byType的方式进行注入

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

止步前行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值