Spring源码分析-如何获取Bean对象
导语
在上篇博客中 介绍了关于BeanFactory和FactoryBean相关的操作,并且查看了在两个操作中他们具体的代码有那些,这篇博客主要就是顺着上篇博客思路继续来分析Bean对象的获取。下面就让我们进入主题
文章目录
- 类继承关系
- 缓存中获取Bean对象
- 从Bean实例中获取对象
- 获取单例
- 准备创建Bean
- 处理Override属性
- 实例化的前置处理
- 总结
类继承关系
在分析Bean对象的获取之前,首先来看一下XmlBeanFactory 的类继承关系图。如下所示,下面的分析也是通过这样类继承关系来进行分析的。
缓存中获取Bean对象
在上篇博客中介绍了FactoryBean的简单用法,并且分析了在doGetBean方法中其实第一步是从缓存汇总加载一个Bean对象。在Spring创建对象的时候默认都是以单实例创建的,后续获取的时候直接从缓存中进行获取,当然这里的操作并不是非要从缓存中获取到内容不可。也是一个尝试性的操作。如果加载失败之后,还是会从ObjectFactory中进行获取。因为在创建单例的Bean对象的时候回存在依赖注入的情况,而在创建依赖的时候为了避免循环依赖,Spring创建Bean的原则就是不等Bean创建完成就会将创建Bean的ObjectFactory提前曝光。加入到缓存中。一旦下一个Bean创建的时候依赖了一个Bean,则就会直接调用ObjectFactory。
/**
* 检查缓存中或者之前工厂中是否存在对应的实例
*
* 在创建单实例的Bean的时候会存在依赖注入的操作,如果已经存在对应的Bean实例,就会
* 出现循环注入的错误
* Spring的原则就是创建Bean的时候不等到Bean创建完成就将BeanFactory曝光,也就是说将BeanFactory加入到对应的缓存中,告诉
* 其他的调用,有对应的BeanFactory来创建该对象。如果需要进行创建的话直接可以从缓存中获取到对应的BeanFactory进行创建对象。
*/
// Eagerly check singleton cache for manually registered singletons.
//尝试从缓存中获取单实例对象或者从BeanFactory中获取
Object sharedInstance = getSingleton(beanName);
在getSingleton()方法中的调用关系如下图所示
在这个方法中,首先尝试从singletonObjects中获取数据
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
如果在singletonObjects中没有找到对应的数据,才会到earlySingletonObjects 中进行查找
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
如果还是获取不到,再尝试从singletonFactories中进行获取beanName对应的ObjectFactory,然后调用这个ObjectFactory 的getObject()方法来创建Bean,并且将Bean对象加入到earlySingletonObjects中,然后从singletonFactories中移除这个ObjectFactory。对于后续的操作也只是在需要的时候再次进行操作,也就是出现再次的循环检查的时候。这个操作的条件就是allowEarlyReference 为true。
从上面的代码中可以卡看到有如下的一些对象需要进行解释一下
- singletonObjects : 用于保存BeanName和创建Bean实例之间的关系;
- singletonFactories:用于保存BeanName和创建Bean的工厂之间的关系
- earlySingletonObjects:也是保存baneName和Bean实例之间的关系但是与singletonObjects不同的是,它的作用就是为了检测循环依赖的问题
- registeredSingletons:用来保存当前所有已注册的bean。
从Bean实例中获取对象
这个是上篇博客getBean的时候第二步操作。其中的bean是一个Object类型。这里调用了getObjectForBeanInstance()方法,在后续的条件判断成功的时候,都是通过这个方法获取Bean对象。最终返回的对象也是从这个方法中进行返回。
//返回对应的实例
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
无论是从缓存中获取到的Bean还是根据不同的作用域获取到的Bean都是在得到之后调用这个方法来检查,相当于检查一下是否是FactoryBean类型的Bean。如果是的话那就要使用getObject()方法的返回值作为Bean对象来返回。如果不是才是按照原来的逻辑执行。
前面提到的,无论是通过什么方法获取到的Bean都只是最原始的Bean并没有进行加工,而我们需要的Bean是需要进行加工之后的。所以在这个过程中加入了很多的加工方法。从上面代码来看getObjectForBeanInstance方法中所完成的工作主要有以下一些
- 1、对FactoryBean的正确性的验证工作
- 2、对非FactoryBean对象不做任何处理
- 3、对Bean进行转换
- 4、将从Factory中解析Bean的工作委托到一个getObjectFromFactoryBean方法中,该方法代码如下
从上面这段代码中,这个方法值做了一件事情,就是返回的bean如果是单实例的,那就需要保证它的全局唯一性,同时因为是单例的,所以不需要进行重复的创建操作,使用缓存就可以极大的提高性能,也就是说如果是已经加载过的Bean就会进行缓存,如果是没有就直接获取。
根据框架的一般编写思想,在getObjectFromFactoryBean的方法中肯定是不会有关键逻辑的,所以会发现在getObjectFromFactoryBean方法中还是有do开头的方法,而这个do开头的方法才是关键中的关键。
在上面这个方法中,看到有一个方法object = factory.getObject();,这个方法就是FactoryBean中的方法,同样我们也知道,在使用Factory的时候我们获取到的就是这个方法最后返回的对象。到这里就真正的有所了解了。
细心的人也许会发现,如果所有的条件都不满足最后进入的是一个object = new NullBean();而在上面的方法中有两段代码都是有进行是否进行后处理操作。
对于后处理操作,在后面的分析中会详细提到它是干什么的,并且会提到它有什么样的作用,这里先来简单的追踪一下它的方法调用关系。
根据上面的代码追踪到了如下的一段代码,会看到这里调用了一个Bean的处理器,也就是说,我们的精细加工的工厂流水线被找到了。在Spring中有这样一条规则,就是尽可能的保证所有Bean初始化后都会调用到BeanPostProcessor 的postProcessAfterInitialization 方法,在实际的开发中,可以使用这个特性来设计自己的业务逻辑。举个栗子,就是我们找到了这个工厂的流水线,如果需要新的东西,我们不需要去重新从工厂级别开始建造,只需要将工厂中的流水线替换就可以了。也就是说Spring虽然是提供了很多的FactoryBean ,但是都是工厂级别的,实际上我们只需要将BeanFactory中的流水线给替换就可以了。
获取单例
上面的分析内容是来自于org.springframework.beans.factory.support.AbstractBeanFactory 的分析,从类名可以看出来,它是一个抽象类,并不会用来创建对象,它里面提供的方法应该是由子类来继承的,当然对于一些公共方法子类进行了继承,不需要重新编写,但是对于有些定制化的方法子类需要重写,或者是子类应该有子类独有的方法,下面就来看看这个抽象类有那些子类。
会看到有三个子类,并且XmlBeanFactory已经是被不推荐使用了,其实这类它的父类是DefaultListableBeanFactory ,也是上面抽象类的子类,也就是说我们分析的时候只需要到DefaultListableBeanFactory 类中分析即可,因为上面第一个类也是一个抽象类。但是实际上获取单例的方法并不是由AbstractBeanFactory 进行提供的,而是由它继承的FactoryBeanRegistrySupport 类和ConfigurableBeanFactory接口提供和很多的实现。并且FactoryBeanRegistrySupport类继承了DefaultSingletonBeanRegistry 类,根据Spring源码的一贯分析原则,隐约的可以感觉到,Spring框架的核心已经离我们不远了在这个org.springframework.beans.factory.support.DefaultSingletonBeanRegistry类中看到一些关于获取单例的方法,并且上面的单例分析获取的方法也是从这个地方继承,只不过上面的方法是从BeanName进行了获取,而下面我们要看到的这个方法是传入的两个参数,一个是BeanName,另一个是ObjectFactory。根据上面的分析,其实在Spring中大部分的对象都是来自于ObjectFactory。
在上面方法中使用了回调方法,也就是说可以在单例创建前后做些准备处理的的操作,而真正的获取单例的方法并没有出现在这个方法中,实际上真正获取单例的方法在之前我们已经分析过了而这个方法的回调在Spring doGetBean 方法中如下图所示
从上面方法的逻辑中可以看到,主要完成了以下的一些操作
- 1、检查缓存是否已经加载过对象了
- 2、如果没有加载,则记录BeanName的正在加载状态
- 3、加载单例前记录加载的状态
- 4、通过调用参数传入的ObjectFactory的个体Object 方法实例化Bean
- 5、加载单例后的处理方法调用
- 6、将结果记录到缓存并且删除加载bean过程中记录的辅助状态
- 7、返回处理结果
从第二张图的调用结果中可以看到,它其中调用了一个createBean()的方法,所以下面就来分析一下这个方法。
准备创建Bean
从方法调用来看它似乎是调用了自己专属的方法,但是实际上方法被实现在了AbstractAutowireCapableBeanFactory 类中,但是实际上,根据以往的经验,这个方法中是不会有什么核心的逻辑的,因为它其中会有doCreateBean这样的方法,这样的方法才是真正的核心操作。下面就来看看在这个方法中是不是有这样的操作。
查看源码发现,还真的在某个地方有这样一个方法Object beanInstance = doCreateBean(beanName, mbdToUse, args)。但是先不忙着看这个方法,先来看看createBean方法的操作有那些
- 1、根据设置的Class属性或者ClassName来解析Class
- 2、对Override属性进行标记验证。这里我们并没有在Spring中看到关于Override的配置,为什么会有这样的操作呢?首先Override表示继承自父类那么在Spring配置中有没有关于继承相关的配置呢?在BeanDefinition中有一个methodOverrides属性,而针对它的配置有两个,一个是lookup-method,另一个是replace-method。
- 3、应用初始化前的后处理,解析指定Bean是否存在初始化前的短路操作
- 4、创建bean。
处理Override属性
在面向对象的编程语言中,不可避免的要处理关于继承相关的操作。在前面提到过一个概念BeanDefinition,也许有人不理解这个BeanDefinition是干什么的,从字面单词翻译来看它就是一个Bean定义的抽象。我们在配置文件中通过xml的方式配置了一个Bean,并且提供了很多的配置项。怎么让这些配置项,与实际的反射结合起来通过前置处理,后置处理生成最后我们想要的Bean对象,这是问题的关键,而BeanDefinition其实就是从XML中的Bean配置,到具体我们想要的Bean对象之间的一个映射关系,只不过这个映射关系可能跟靠近与配置文件。也就是是我们生成Bean对象的原料。
对于Override属性的处理,根据上面的分析可能是来自于BeanDefinition,顺着这个思路,进入到BeanDefinition中,在AbstractBeanDefinition中发现了具体的处理实现。
通过上面的两个方法结合方法上的英文注释,可以知道,Spring中提供了lookup-method和replace-method 两个配置功能,就是将配置放置到methodOverrides属性里面,这两个功能实现的原理就是在Bean实例化的时候如果检测到了该属性,就会动态的为当前Bean生成代理并且使用对应的拦截器对Bean进行增强处理。下面通过一个小例子来看看
首先来创建一个接口
public interface User {public void showMe();
}
编写两个测试类
public class Teacher implements User {public void showMe() {System.out.println("I am a Teacher");}
}
public class Student implements User {public void showMe() {System.out.println("I am a Student");}
}
编写测试对象
public abstract class MyTestBean {//用于测试lookup-methodpublic abstract User getUserBean();//用于测试 replace-methodpublic void changedMethod(){System.out.println("Origin method in MyTestBean run");}
}
public class Replacer implements MethodReplacer {public Object reimplement(Object o, Method method, Object[] objects) throws Throwable {System.out.println("replacer method run");return null;}
}
编写配置XML文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="myTestBean" class="com.core.charp2.demo03.testbean.MyTestBean"><lookup-method name="getUserBean" bean="student"/><replaced-method name="changedMethod" replacer="replacer"/></bean><bean id="teacher" class="com.core.charp2.demo03.bean.Teacher"/><bean id="student" class="com.core.charp2.demo03.bean.Student"/><bean id="replacer" class="com.core.charp2.demo03.testbean.Replacer" /></beans>
编写测试主类
public class Test {public static void main(String[] args) {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:demo3.xml");MyTestBean myTestBean = (MyTestBean) applicationContext.getBean("myTestBean");myTestBean.getUserBean().showMe();myTestBean.changedMethod();}
}
通过上面的小例子可以看到两个配置是如何使用的,但是对于方法的匹配来讲,如果一个类中存在若干个重载方法,那么在函数调用以及增强的时候还需要根据参数类型进行匹配,最终来确定当前到底调用的是哪个函数。但是Spring将一部分的匹配工作通过上的BeanDefinition的方式完成,如果当前类中方法只有一个,那么就设置该重载方法没有被重载,在后续的调用中就可以直接查找对应的方法了,这样就剩了一步参数匹配验证的操作,这样可以说在效率上有了很大的提升。
实例化的前置处理
在真正调用doCreate方法用来去创建一个Bean实例之前会调用一个方法resolveBeforeInstantiation()对于Beandefinition中的属性提前做一些处理工作。当然对于这些提前处理的结果,是否有对应的处理都可以,因为即使没处理,在后续的处理中也会加入处理逻辑,这也体现了Spring在Bean创建的前后的扩展性。
从上面的逻辑中可以看到有一步操作,是关于判空的操作,当经过前置处理器的时候返回的结果不为空,那么就会直接跳过后续对于Bean的操作直接返回结果,这个在使用的时候经常是被忽略的一个地方,我们所熟知的AOP功能就是在这里进行判断会看到在下面的代码中调用了两个关键方法applyBeanPostProcessorsBeforeInstantiation()和applyBeanPostProcessorsAfterInitialization()。这两个方法也是很容易理解,就是在对后置处理器中所有的InstantiationAwareBeanProcesser类型的后处理器进行处理和对BeanPostProcessor的处理。
1、实例化前的后处理器应用
Bean的实例化前,也就是将AbstractBeanDefinition转换成为BeanWrapper前的吹李,需要给子类一个修改BeanDefinition的机会,也就是说当程序调用这个方法的时候,Bean可能不是我们认为的哪个Bean,也许是经过一个代理进行了处理,这就是动态代理。在这个操作之前调用了如下的方法
2、实例化后的后处理应用
上面提到过从缓存中获取单例Bean的时候,Spring中的规则是在Bean的初始化后尽可能保证将注册的后处理器postProcessAfterInstantiation方法应用到该Bean中,因为如果返回的bean不为空,那么就不会在进行一次Bean对象的创建,所以只能是在这里使用后处理器的postProcessAfterInstantiation方法
总结
到这里如何获取Bean对象就算完成了。后续的操作就是拿到这个Bean对象之后,对这个Bean对象进行前置的加工和后置的处理,在后续的分析中进行进一步的跟进。来看看在Spring中到底是如何让我们获取到最后的哪个我们想要的Bean对象的。
Spring源码分析-如何获取Bean对象相关推荐
- Spring IOC 容器源码分析 - 创建原始 bean 对象
1. 简介 本篇文章是上一篇文章(创建单例 bean 的过程)的延续.在上一篇文章中,我们从战略层面上领略了doCreateBean方法的全过程.本篇文章,我们就从战术的层面上,详细分析doCreat ...
- 【Spring源码分析系列】bean的加载
前言 以 BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beans.xml"));为例查看bean的加载过 ...
- Spring源码分析——汇总全集
文章目录 一.背景 二.源码分析目录 三.源码番外篇(补充) 更新时间 更新内容 备注 2022-04-01 Spring源码分析目录和计划 2022-04-10 Spring源码分析一:容器篇-re ...
- 【Spring源码分析】Bean加载流程概览
代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...
- beaninfo详解源码解析 java_【Spring源码分析】Bean加载流程概览
代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...
- Spring源码分析之Bean的创建过程详解
前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...
- Spring源码分析系列——bean创建过程分析(三)——工厂方法创建bean
前言 spring创建bean的方式 测试代码准备 createBeanInstance()方法分析 instantiateUsingFactoryMethod()方法分析 总结 spring创建be ...
- 【spring源码分析】IOC容器初始化(二)
前言:在[spring源码分析]IOC容器初始化(一)文末中已经提出loadBeanDefinitions(DefaultListableBeanFactory)的重要性,本文将以此为切入点继续分析. ...
- Spring 源码分析(三) —— AOP(五)创建代理
2019独角兽企业重金招聘Python工程师标准>>> 创建代理 代理的定义其实非常简单,就是改变原来目标对象方法调用的运行轨迹.这种改变,首先会对这些方法进行拦截,从而为这些方法提 ...
最新文章
- 关于Spark NLP学习,你需要掌握的LightPipeline(附代码)| CSDN博文精选
- 岚图FREE入局之战,手握哪些底牌?
- 机器学习-数据科学库(第六天)
- android如何引用布局,android 动态布局与引用第三方layout中的布局
- LeetCode 873. 最长的斐波那契子序列的长度(动态规划)
- 零基础小白10分钟用Python搭建小说网站!网友:我可以!
- ajax分批mysql_使用select2分批异步加载大量数据
- linux怎么看系统盘,Linux系统怎么查看电脑的磁盘空间?
- 使用数据分析工具的注意事项
- 删除本地oracle数据库,如何在WINDOWS 2000将ORALCE完全卸载-数据库专栏,ORACLE
- 算法的优缺点_逻辑回归算法的优缺点
- 怎么写linux的sh文件,linux – 什么是.sh文件?
- linux 下竟有想大白菜软件,大白菜U盘做PE及CDLINUX镜像共存的启动盘.pdf
- idea报错 No valid Maven installation found.maven不能用
- js中Array对象。concat,concat,pop,push,reserve,shift,slice,splice,toString,toLocaleString,unshift
- 罗马数字(Python)
- 宁波诺丁汉计算机学院,宁波诺丁汉大学学子帝国理工计算机录取
- linux 通过手机上网,Linux系统通过手机GPRS上网设置简介有哪些呢?
- js获取摄像头权限实现拍照功能
- 光电玻璃LED透明屏是黑科技?揭秘玻璃LED透明屏原理
热门文章
- 14-磁盘管理-df,du命令,磁盘分区
- Seven times have I despised my soul 《我曾七次鄙视自己的灵魂》
- 动画分析步骤“三步曲”
- ERROR: Minions returned with non-zero exit code
- Perl中的正则表达式
- 无意间发现我的博客园的年龄有11年了
- 云计算的思想领袖:与Tier3的创始人和首席技术官Jared Wray的谈话
- 26个要素,仅仅依靠百度打造成功网站
- JBookManager v1.00.2008314 (编辑管理您的Jar电子书)
- 关于过程和线程的常识点汇总