IOC是如何对外提供Bean的

在这里插入图片描述

博主 默语带您 Go to New World.
个人主页—— 默语 的博客👦🏻 优秀内容
《java 面试题大全》
《java 专栏》
《idea技术专区》
《spring boot 技术专区》
《MyBatis从入门到精通》
《23种设计模式》
《经典算法学习》
《spring 学习》
《MYSQL从入门到精通》数据库是开发者必会基础之一~
🍩惟余辈才疏学浅,临摹之作或有不妥之处,还请读者海涵指正。☕🍭
🪁 吾期望此文有资助于尔,即使粗浅难及深广,亦备添少许微薄之助。苟未尽善尽美,敬请批评指正,以资改进。!💻⌨


默语是谁?

大家好,我是 默语,别名默语博主,擅长的技术领域包括Java、运维和人工智能。我的技术背景扎实,涵盖了从后端开发到前端框架的各个方面,特别是在Java 性能优化、多线程编程、算法优化等领域有深厚造诣。

目前,我活跃在CSDN、掘金、阿里云和 51CTO等平台,全网拥有超过10万的粉丝,总阅读量超过1400 万。统一 IP 名称为 默语 或者 默语博主。我是 CSDN 博客专家、阿里云专家博主和掘金博客专家,曾获博客专家、优秀社区主理人等多项荣誉,并在 2023 年度博客之星评选中名列前 50。我还是 Java 高级工程师、自媒体博主,北京城市开发者社区的主理人,拥有丰富的项目开发经验和产品设计能力。希望通过我的分享,帮助大家更好地了解和使用各类技术产品,在不断的学习过程中,可以帮助到更多的人,结交更多的朋友.


我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。


抽象类AbstractBeanFactory确定了BeanFactory的基本流程,IOC中创建Bean的入口也在该类中定义:getBean,该方法又调用doGetBean方法来定义具体的实现流程。想要搞懂具体的流程需要先明白以下几点:

**BeanFactory:**Bean–>Factory,以Factory结尾标识是一个生产Bean的工厂类,统领IOC容器中所有Bean的获取入口,用于对外提供实现DI,但是BeanFactory的主要功能是作为IOC的门面,也就是直接与开发者进行交互,例如:DefaultListableBeanFactory,XmlBeanFactory,ApplicationContext

**FactoryBean:**Factory–>Bean,以Bean结尾标识是一个Bean,但这个Bean具有Factory功能,也就是一个具有构建其他Bean能力的特殊Bean,虽然有了BeanFactory,但是BeanFactory主要作为门面,而且BOP主要面向对象就是Bean,对于Bean的构建流程,需要一个专门的类来负责Bean,这个类就是FactoryBean。

**BeanDefinition:**Bean的定义信息,FactoryBean想要构建Bean就需要基本的构建参数,而BeanDefinition就是用来存储一个Bean所有的相关信息的,例如:类名、作用域、初始化方法、属性、依赖的Bean名称、是否单例、是否延迟加载等,相当于Bean的类描述文件,FactoryBean根据这些信息来构建Bean的,BeanDefinition中任意一个配置都直接决定了开发者最终拿到是的Bean。实现类中:RootBeanDefinition是spring内部构建Bean时的统一视图,也就是所有的Bean都是通过RootBeanDefinition中配置的信息来构建的,如果提供的是其他实现,例如GenericBeanDefinition、ChildBeanDefinition则需要处理成统一视图RootBeanDefinition再去构建。

以上3点总结一句就是:BeanFactory 让 FactoryBean 拿着BeanDefinition去构建Bean,并把这个Bean交给BeanFactory用于BeanFactory提供开发者使用

下面开看下具体的构建流程AbstractBeanFactory#getBean --> doGetBean,不贴代码,只看思路。

开发者对IOC提供条件要拿一个Bean,例如该条件是BeanName,IOC调用doGetBean开始获取最终Bean。

1.先把BeanName清洗下,保证拿到的是Bean的真正名称(统一化名称),例如:将别名查找、&截取等,之后拿到一个Bean的统一化名称

2.根据清洗后的BeanName尝试获取从单例三级缓存中一个实例化对象sharedInstance

3.1.如果获取到的sharedInstance不为空,则判断sharedInstance是否为FactoryBean,如果是则需要走FactoryBean的Bean构建流程

3.2.如果获取到的sharedInstance为空,则先去父容器中找,找不到则需要根据BeanDefinition去构建Bean了

4. 处理依赖Bean,然后通过getMergedLocalBeanDefinition获取到RootBeanDefinition,然后去实例化

4.1.如果Bean是单例,则触发单例获取流程:getSingleton

4.2. 如果Bean是原型,则直接构建新的实例,触发后续流程

4.3 如果Bean是其他作用域,如:request,session、application,也是构建新的实例,绑定到对应的作用域上

主流程其实就这4步:1.获取Bean统一化名称,2:尝试从单例注册表中获取缓存的实例,3:尝试从父容器中获取 4.依赖处理、作用域获取对应实例

1:获取Bean统一化名称

这一步就是开发者传入的name有可能是别名,有可能是&开头的FactoryBean,因此需要获取Bean真正的标准名称

2:尝试从单例注册表中获取缓存的实例

在单例Bean的循环依赖解决方案中存在三级缓存,其中一级缓存就是存储可以对开发者对外提供的Bean,二级缓存是半成品Bean,三级缓存则是FactoryBean,先尝试从缓存中获取实例,减少重复获取

3:尝试从父容器中获取

子容器可以访问父容器中的Bean,而父容器不能使用子容器中的Bean,这里就是原因。

4:依赖处理、作用域获取对应实例,作用域划分:单例,原型、Request域、Session域、application域。

单例singleton:整个IOC中独此一份,所有DI提供的Bean都是一个实例,这种Bean在首次创建后会被缓存到一级缓存单例注册表中

多例prototype:IOC对外提供的Bean的实例都不一样,都是全新的Bean,不受影响

Request域:在Web环境下,一次请求到来至响应结束之间有效

Session域:在Web环境下,同一个SessionId会话追踪之间有效

application域:在Web环境下,应用正常服务中都有效

Bean的构建核心流程在第四步

Bean的统一化定义视图RootBeanDefinition

Bean的定义信息来源很多,例如,GenericBeanDefinition、ChildBeanDefinition、AnnotatedGenericBeanDefinition、ScannedGenericBeanDefinition,它的所有实现都是为了给开发者提供方便,如果开发者同时使用了多种方式的BeanDefinition去描述一个类,这种方式也是可接受的,处理的关键就是在构建bean之前进行了MergedBeanDefinition,也就是“合并”,合并结果就是形成了统一视图RootBeanDefinition。

合并规范:BeanDefinition具有一个parentName属性,这个是连接多个BeanDefinition的点,根据子找父关系找到最顶层父BeanDefinition,用该BeanDefinition构建一个RootBeanDefinition,然后根据父找子关系逐层往下找子BeanDefinition,用子BeanDefinition去覆盖RootBeanDefinition,其中 构造器参数、属性子类多的会添加进去,其他的以子类的定义信息为主。

单例Bean获取:
// 简化后
sharedInstance = getSingleton(beanName, () -> {return createBean(beanName, mbd, args);});

getSingleton方法主要是将FactoryBean构建的单例Bean放入单例注册表进行缓存,核心在createBean,其实现在AbstractAutowireCapableBeanFactory中,然后交给doCreateBean处理,处理整体流程如下

1. 根据构造方法反射获取实例(支持构造器自动注入),createBeanInstance

实例化之后有一个BeanDefinition的后置处理,用来处理之前合并后的RootBeanDefinition

2. 内部解决循环依赖

如果允许内部解决Bean的循环依赖问题,到这一步就会把这个实例给提前曝光出去,这样当其他Bean依赖该Bean时就可以获取到该Bean的引用,从而能完成依赖,曝光方式为:会注册一个该Bean的singletonFactory到单例循环依赖解决方案的三级缓存中

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

循环依赖的最终获取Bean的方法就是这个匿名方法,

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
	Object exposedObject = bean;
	if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
		for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
			exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
		}
	}
	return exposedObject;
}

这里主要在exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);这一句,让SmartInstantiationAwareBeanPostProcessor去后置处理如何去曝光半成品的Bean,默认实现是什么也不处理,直接返回,而AOP则在AbstractAutoProxyCreator中重写了该方法,返回一个代理对象。

以上功能就是如果允许框架自行解决循环依赖,就会通过一个getEarlyBeanReference方法将构建的实例提前曝光,曝光的实例默认是反射出的实例,而AOP则重写了该方法返回了一个新的代理实例。提前曝光注重的是实例的引用地址。

3. 属性设置

属性填充,包括依赖的属性、Autoware的属性等

4.初始化

如果该bean实现BeanNameAware、BeanClassLoaderAware、BeanFactoryAware,则进行调用完成Aware,就是将对应的属性set

然后在init-method调用前后执行PostProcessorsXXXInitialization处理器, 而且在init-method执行之前又进行了afterPropertiesSet调用

5.可销毁注册

不是原型模式,并且 (实现DisposableBean接口以及指定了destroy方法或者注册了DestructionAwareBeanPostProcessors),后续在销毁的时候 ,就可以运行后置处理相关的业务

以上步骤完成后将获取一个Bean实例对象,这个对象还需要经过getObjectForBeanInstance处理才能对外提供,根据Bean是普通Bean还是工厂Bean,然后对外提供

基于实例对象构建Bean

protected Object getObjectForBeanInstance(Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd)

beanInstance:以上步骤获取的一个实例化对象,可以是直接反射出的实例,也可以是AOP创建的代理

name: 开发者提供的name,支持&开头的FactoryBean

beanName: 统一化名称

mdb: 合并后的Bean定义信息

如果是为了获取FactoryBean的实例,此时的beanInstance就已经满足了条件

如果为了获取普通的Bean,并且beanInstance不是一个FactoryBean,则该实例也满足了条件

如果为了获取普通的Bean,而此时的beanInstance是一个FactoryBean,则需要FactoryBean构建一个实例返回

基于FactoryBean对象构建Bean

protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess)

factory:FactoryBean,上面的BeanInstance

beanName: 统一化名称

shouldPostProcess:是否进行后置处理,如果是程序定义的则需要处理

执行过程就是factory.getObject(),一般还会进行后置处理applyBeanPostProcessorsAfterInitialization

非单例Bean获取

单例与其他作用域的区别在于生命周期不同,因此单例通过getSingleton进行缓存来保证单例,其他作用域则不需要缓存,都是多例Bean,只是绑定了作用域,每次获取都要走一遍单例获取中的createBean方法。

在这里插入图片描述


🪁🍁 希望本文能够给您带来一定的帮助🌸文章粗浅,敬请批评指正!🍁🐥

如对本文内容有任何疑问、建议或意见,请联系作者,作者将尽力回复并改进📓;(联系微信:Solitudemind )

点击下方名片,加入IT技术核心学习团队。一起探索科技的未来,共同成长。

在这里插入图片描述

更多推荐