死磕Spring原理 -- 注入过程详解

本文着重分析执行流程。

DI流程简介

  1. ioc容器:BeanFactory,工厂容器
  2. 初始化获取BeanDefinition:InstantiationStrategy,实例化策略
  3. 依赖注入:AbstractPropertyAccessor,属性访问器,用于各种方式的注入,如setter方法注入,构造器注入等
  4. 实例化Bean:BeanWrapper,Bean的包装类

DI过程概述

  • 核心方法getBean()
  • 非lazyInit在refresh的方法finishBeanFactoryInitialization中执行
  • lazyInit时,用到才加载

注入方式

  1. 按名称注入
    getBean(“name”):按名字注入过程,核心方法AbstractBeanFactory.doGetBean()
  2. 按类型注入
    getBean(xxx.class):按类型注入,核心方法DefaultListableBeanFactory.resolveBean()

按名称注入流程描述:

普通的单例Bean注入流程简述:(非代理情况,如AOP)

AbstractBeanFactory.getBean()
->doGetBean()
->DefaultSingletonBeanRegistry.getSingleton() //查询缓存,并标记各种状态,比如正在创建的状态
->AbstractAutowireCapableBeanFactory.createBean() //在上一步的调用过程中执行
->doCreateBean()
->createBeanInstance()//创建BeanWrapper,此步中,如果使用构造器注入,则会跳到处理成员的过程中,即getBean,直到成员初始化完成,再回来,此处是循环依赖处理的重要步骤
->populateBean() //填充BeanWrapper,如果有成员也是Bean,则去加载其他成员Bean,也会调用getBean

//普通的单例bean到这里也就结束了,但是如果需要使用动态代理的时候,后面还有一个方法会生成一个代理,来替代当前的Bean。

按类型注入流程描述:

  1. 目标是去寻找NamedBeanHolder这个实例,里面包含一个name和bean实例
  2. 会拿到传入的类型去BeanFactory里检查两个bean名称的缓存allBeanNamesByTypesingletonBeanNamesByType(二选一)。这两个map是以class:name[]形式存储的,也就是说会根据类型去获取bean的所有名称(比如默认的名称,别名等)
  3. 然后根据这个名称去调用getBean(name)的方法去获取Bean实例,组装成NamedBeanHolder返回。
  4. 如果那两个map里没有对应的class,那么会去拿到所有的BeanNames去遍历并检查类型是否匹配,如果匹配,则返回所有匹配的name,并且在当前选择的map里写进去一个缓存。
  5. 经过上一步操作,如果还是没有获取到对应的name,那么就会调用当前BeanFactory的parent或者parent的ObjectProvider去处理这个类(一般在扩展的情况下才会用到)
  6. 拿到所有的名字以后,会去检查,这个bean是不是单例,是的话调用getBean,放进当前缓存
  7. 如果不止一个bean符合,那么检查是不是标注Primary,是的话,用标注了primary的Bean的名字;如果不是,检查优先级,拿优先级最高的bean的名字
  8. 然后根据当前获取到的名字,去当前的备选bean缓存中获取bean,然后封装成NamedBean返回

循环依赖问题产生条件:

  1. 使用构造器注入
  2. A类在构造器中需要传入B类,同理,B也需要A
  3. 没有使用注解@Lazy
加载流程:
sharedInstance = getSingleton(beanName, () -> {
	try {
		return createBean(beanName, mbd, args);
	}catch (BeansException ex) {
		...
	}
});
  • 先加载A,执行到上面这段代码时,getSingleton先执行,在此方法中会标记当前bean正在创建中。标记是在beforeSingletonCreation(beanName)
  • 发现A的带参构造器里需要使用B,然后对B进行解析加载,resolveDependency中最后一段,getLazyResolutionProxyIfNecessary方法用于检查是不是用了懒加载的处理方法,如果不是则直接去解决这个依赖
Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, requestingBeanName);
if (result == null) {
	result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
}
  • doResolveDependency方法中,有一个resolveCandidate方法;在此方法之前,会获取到很多符合要求的备选Bean名称,而这一步是在能够找到这些名称后的最终方法,解决备选Bean,而它实际就是BeanFactory.getBean
  • 于是现在去加载B,重复上面的步骤,会再一次的去加载A,发现A已经被标记为正在创建中,于是爆出循环依赖的问题
流程概括:A和B相互依赖,构造器为A(B),B(A)
  1. 加载A,标记A在创建中
  2. 调用A(B)构造,发现B被依赖
  3. 加载B,标记B在创建中
  4. 检查B是否使用@Lazy,没有的话,调用B(A)构造,发现A被依赖
  5. 加载A,标记A时,发现A在创建中,循环依赖
  6. 异常

循环依赖解决方案之使用@Lazy

循环依赖的解决方法有几个:

  1. 解藕代码,分离循环依赖的属性
  2. 使用属性注入
  3. 使用@Lazy

具体说第三种。在写法上,下面两种都是可以的,也没啥区别。

@Lazy
public A(B b) {
	this.b = b;
}

public A(@Lazy B b) {
	this.b = b;
}
原理说明

如上面讲述,循环依赖产生的流程,在getLazyResolutionProxyIfNecessary调用到这里时,该方法先检查了是否是懒加载,如果是则创建了一个Cglib的代理对象返回。也就是说流程变成了

  1. 加载A,标记A在创建中
  2. 调用A(B)构造,发现B被依赖
  3. 加载B,标记B在创建中
  4. 检查到B是否使用@Lazy,是的话,创建一个Cglib的动态代理
  5. 解决了B的创建,返回到A的构造中,注入B的代理
  6. 完成A的实例的创建

需要注意的是,在实际spring项目中,A被创建以后,在A里的成员B永远都是动态代理。
但是在使用A涉及到B时,会被spring实现的MethodInterceptor拦截并处理,将B动态代理的地址转换为实际的B。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值