spring源码系列(四)——beanDefinition(2)

在上一篇文章里面咱们讨论了一个beanDefintion对象的重要性,为了讨论spring当中的beanDefinition对象咱们不得不牵扯出spring当中的bean工厂后置处理器也就是BeanFactoryPostProcessor这个类;继而讨论了BeanFactoryPostProcessor的大概执行时机(BeanFactoryPostProcessor的执行时机很重要并且spring内部作的稍微有点复杂,本文重点来讨论spring内部怎么保证这些执行时机得以严禁的执行,还有如何来扩展spring的bean工厂后置处理器);首先经过一张图简单的理解一下spring容器启动的时候执行调用BeanFactoryPostProcessor后置处理器的大概的方法执行顺序java

上图大概分为④步(这里只是讨论spring如何调用BeanFactoryPostProcessor,在调用以前到底执行了那些方法,上图并非spring容器启动的全部步骤) 启动main方法,在main方法里面调用 AnnotationConfigApplicationContext的无参构造方法,而后②-1在无参构造方法里面首先实例化AnnotatedBeanDefinitionReader对象,实例化这个对象有两个很是重要的意义。  一、这个对象实例化出来有什么用(问题一) ;  二、实例化这个对象的过程当中作了哪些事情(问题二)程序员

全部的问题X,文章后面再解释,这里先把步骤搞清楚spring

继而②-2spring又实例化了一个ClassPathBeanDefinitionScanner对象,这个对象顾名思义就是可以用来完成spring的扫描功能,可是这里提一句——spring内部完成扫描功能并非用的这个对象,而是在扫描的时候又new了一个新的ClassPathBeanDefinitionScanner对象;换言之这里spring new的对象咱们假设他为a,可是spring在真正完成扫描的时候又new了一个b,可是是同一个类都是ClassPathBeanDefinitionScanner,可是为何须要两个? 三、直接用a不就能够了吗?若是spring内部完成扫描时候没用a,那么a被new出来在哪里使用了?(问题三)到此为止第②步完成; 调用register(Appconfig.class);首先会把Appconfig类解析成为一个beanDefintion对象(如何把一个类解析称为beanDefinition对象的?这里其实涉及到②-1AnnotatedBeanDefinitionReader对象的意义,若是你明白了②-1这里也就明白了),而后给解析出来的beanDefinition对象设置一些默认属性,继而put到beanDefintionMap当中;为何须要put到beanDefintionMap呢?在上一篇咱们已经解释过这个map就是单纯用来存储beanDefinition的,spring后面会遍历这个map根据map当中的beanDefinition来实例化bean,若是Appconfig类的beanDefintion存在在map当中那么他必然会被spring容器实例化称为一个bean?为何Appconfig会须要实例化呢?由于Appconfig当中有不少加了@Bean的方法,这些方法须要被调用,故而须要实例化,可是Appconfig类的实例化很复杂比通常类实例化过程复杂不少,涉及到代理涉及到cglib等等,这个咱们后面文章解释;这里还须要解释一个问题,为何Appconfig类是经过register(Appconfig.class);手动put到map当中呢?为何不是扫描出来的呢?(通常类都是经过扫描出来的),其实也很简单,由于他没法扫描本身,通常类是spring经过解析Appconfig上的@ComponentScan注解而后被扫描到,因此没法扫描本身;接下来即是第 步,④-1到④-4之后分析,④-5即是咱们上篇文章说的执行spring当中的bean工厂后置处理器,也是本文重点讨论的;下图是对上述文字的一个说明——spring容器启动的执行顺序 编程

接下来解释上面文字里的全部问题 问题一——AnnotatedBeanDefinitionReader这个对象实例化出来有什么用? 先看一下这个类的javadoc,看看做者怎么来解释这个类的 api

Convenient adapter for programmatic registration of annotated bean classes. * This is an alternative to {@link ClassPathBeanDefinitionScanner}, applying * the same resolution of annotations but for explicitly registered classes onlybash

按照笔者蹩脚的英文水平个人理解是:这个类做用分为如下两个 一、可用于编程式动态注册一个带注解的bean,什么意思呢?好比咱们有一个类A存在com.shadow包下面,而且是一个加注解的类。好比加了@Component,正常状况下这个类A通常是被spring扫描出来的,可是有不正常状况,好比spring并无扫描到com.shadow包,那么类A就没法被容器实例化。有人可能会问为何没有扫描到com.shadow包?扫描状况下不会扫描到?其实很简单,假设你的这个类是动态生成,在容器实例化的时候不存在那么确定不存在,再或者这个包下面有N多类可是只有一个类加了注解,那么其实你不须要去扫描,只须要添加这一个加了注解的类便可,再或者一个类是你和第三方系统交互后获得的。那么这个时候咱们能够把这个类经过AnnotatedBeanDefinitionReader的register(Class clazz)方法把一个带注解的类注册给spring(这里的注册其实就是上一篇文章中说的把一个类解析成BeanDefintion对象,而后把这个对象put到beanDefinitionMap当中),写个例子来测试一下他的这个注册bean的功能 app

二、能够代替ClassPathBeanDefinitionScanner这个类,具有相同的注解解析功能, ClassPathBeanDefinitionScanner是spring完成扫描的核心类,这个我后面会分析;简而言之,spring完成扫描主要是依靠ClassPathBeanDefinitionScanner这个类的对象,可是AnnotatedBeanDefinitionReader能够替代他完成相同的注解解析,意思就是经过ClassPathBeanDefinitionScanner扫描出来的类A和经过AnnotatedBeanDefinitionReader显示注册的类A在spring内部会一套相同的解析规则;这点上面那个例子已经证实了。测试

那么AnnotatedBeanDefinitionReader除了动态显示注册一些spring扫描不到的类以外还有什么功能?在初始化spring容器的过程当中他主要干了什么事情呢?或者这么说:假设程序中没有须要动态显示注册的类他就没用了吗?再或者AnnotatedBeanDefinitionReader这个类的对象除了注册一些本身的类还有什么应用场景呢?——注册咱们的配置类,对于不理解是什么配置的读者能够理解所谓的配置类就是那个加了@Configuration和@ComponentScan的那个类,也就是Appconfig.java; 那么问题来了,为何配置类须要手动注册呢?很简单由于配置类没法扫描出来,因此须要咱们手动注册。为何没法扫描呢?好比spring完成扫描是须要解析Appconfig.java当中的@ComponentScan注解的值(通常是一个包名),获得这个值以后去扫描这个值所表明的包下面的全部bean;简单的说就是spring若是想要完成扫描必须先提供Appconfig.java,因此Appconfig.java要在一开始就手动注册给spring,spring获得Appconfig.class以后把他解析成BeanDefintion对象,继而去获取@ComponentScan的值而后才开始扫描其余bean;spa

总结:AnnotatedBeanDefinitionReader的做用一、主要是能够动态、显示的注册一个bean;二、并且具有解析一个类的功能;和扫描解析一个类的功能相同;AnnotatedBeanDefinitionReader的应用场景一、能够显示、动态注册一个程序员提供的bean;二、在初始化spring容器的过程当中他完成了对配置类的注册和解析功能;翻译

针对AnnotatedBeanDefinitionReader应用场景的第2点,我在啰嗦几句,通常程序员在初始化spring容器的时候代码有不少种写法,但都是换汤不换药的,我这里举2个栗子。 第一种写法:

AnnotationConfigApplicationContext ac =
				new AnnotationConfigApplicationContext();

		//动态注册一个配置类
		ac.register(Appconfig.class);

		//调用refresh方法
		//这里不少资料都叫作刷新spring容器
		//可是我以为不合适,这是硬核翻译,比较生硬
		//笔者以为理解为初始化spring容器更加精准
		//至于为何之后慢慢更新再说
		ac.refresh();

复制代码

第二种写法:

AnnotationConfigApplicationContext ac =
		new AnnotationConfigApplicationContext(Appconfig.class);

复制代码

这两种写法都初始化spring容器,代码上的区别无非就是第一种写法是调用AnnotationConfigApplicationContext();的无参构造方法,第二种写法是调用了AnnotationConfigApplicationContext(Class<?>... annotatedClasses)有参构造方法;可是你若是翻阅源码第二种写法的内部也是首先调用无参构造方法的,继而内部继续调用register(Appconfig.class)方法,最后调用refresh();方法,和第一种写法的区别是register和refresh是程序员调用的;笔者更推荐第一种写法,由于第一种写法能够在初始化spring容器以前获得AnnotationConfigApplicationContext的对象也就是代码里面的ac对象;用一张图来讲明一下这两种写法的异同

其实第一种方法和第二种方法均可以获得ac对象,那么为何第一种写法笔者推荐呢?首先第二种方法是在spring容器完成初始化以后的到的ac对象,容器已经初始化了,这个时候获得这个对象能干了事情少了不少;第一种方法在初始化以前获得的,那么能干的事情可多了。 ①好比咱们能够在容器初始化以前动态注册一个本身的bean,就是上文提到的 AnnotatedBeanDefinitionReader的应用场景, ②再好比能够利用ac对象来关闭或者开启spring的循环依赖,在笔者的第一篇博客里面也有提到, ③还好比程序员能够在容器初始化以前注册本身实例化的BeanDefinition对象。若是你像笔者同样精通spring源码你会发觉提早获得这个ac对象能够作的事情太多了,笔者这里说的三个好比都必须在spring容器初始化以前作才有意义,简而言之就是须要在spring调用refresh方法之间作才有意义;若是你不理解我打个比较污的比方; 假设有机会约到某姓网红,无比兴奋以后如今来分析原由和最终结果;原由是她愿意出来和你约会,结果是约会完成最终她仍是要回家的。可是过程能够很精彩呀!对比spring的源码原由是你首先成功实例化了一个ac对象(ac成功实例化==你成功约会==new出来的ac对象==约出来的乔姑娘)`,结果是调用ac.refresh()完成初始化spring容器,至关于乔姑娘回家==完成约会;

那么你想一下第一种方式是你成功实例化一个ac对象,在完成初始化以前能够肆意妄为,除了对ac这个对象随心所欲(调用ac的api)以外还能够写任何你本身想写的代码;至关于你成功约到乔姑娘,而后****************你能够拿到她的身份证。可是第二种方式至关于你约到了乔姑娘,可是约会的过程你没办法参与都是姑娘本身安排,她可能和你看完电影以后就告诉你身份证丢了而后各回各家;因此笔者推荐使用第一种方式实例化spring容器。。。。至于缘由我已经说的够清楚了。

到此为止已经解释完了问题一——AnnotatedBeanDefinitionReader这个对象实例化出来有什么用?

那么问题二问题三 下篇文章讨论,为了说清楚spring当中BeanDefintion笔者已经写了两篇博客(根本没有进入BeanDefintion的主题),以前说过会用三篇文章来分析,看来须要食言了,想了一下至少须要五篇才能说清楚

相关文章
相关标签/搜索