读懂spring源码——beanDefinition(2)

大家好,我是子路,一个靠Java吃饭的男人

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

上图大概分为④步(这里只是讨论spring如何调用BeanFactoryPostProcessor,在调用之前到底执行了那些方法,上图并不是spring容器启动的所有步骤)
启动main方法,在main方法里面调用
AnnotationConfigApplicationContext的无参构造方法,然后②-1在无参构造方法里面首先实例化AnnotatedBeanDefinitionReader对象,实例化这个对象有两个非常重要的意义。

1、这个对象实例化出来有什么用(问题一)
2、实例化这个对象的过程中做了哪些事情(问题二)

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

继而②-2spring又实例化了一个ClassPathBeanDefinitionScanner对象,这个对象顾名思义就是能够用来完成spring的扫描功能,但是这里提一句——spring内部完成扫描功能并不是用的这个对象,而是在扫描的时候又new了一个新的ClassPathBeanDefinitionScanner对象;换言之这里spring new的对象我们假设他为a,但是spring在真正完成扫描的时候又new了一个b,但是是同一个类都是ClassPathBeanDefinitionScanner,但是为什么需要两个?

3、直接用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,看看作者怎么来解释这个类的

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 only

按照笔者蹩脚的英文水平我的理解是:这个类作用分为以下两个

1、可用于编程式动态注册一个带注解的bean,什么意思呢?比如我们有一个类A存在com.shadow包下面,并且是一个加注解的类。比如加了@Component,正常情况下这个类A一般是被spring扫描出来的,但是有不正常情况,比如spring并没有扫描到com.shadow包,那么类A就无法被容器实例化。

有人可能会问为什么没有扫描到com.shadow包?扫描情况下不会扫描到?其实很简单,假设你的这个类是动态生成,在容器实例化的时候不存在那么肯定不存在,再或者这个包下面有N多类但是只有一个类加了注解,那么其实你不需要去扫描,只需要添加这一个加了注解的类即可,再或者一个类是你和第三方系统交互后得到的。

那么这个时候我们可以把这个类通过AnnotatedBeanDefinitionReader的register(Class clazz)方法把一个带注解的类注册给spring(这里的注册其实就是上一篇文章中说的把一个类解析成BeanDefintion对象,然后把这个对象put到beanDefinitionMap当中),写个例子来测试一下他的这个注册bean的功能

2、可以代替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;

总结:AnnotatedBeanDefinitionReader的作用1、主要是可以动态、显示的注册一个bean;
2、而且具备解析一个类的功能;和扫描解析一个类的功能相同;AnnotatedBeanDefinitionReader的应用场景1、可以显示、动态注册一个程序员提供的bean;2、在初始化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的主题),之前说过会用三篇文章来分析,看来需要食言了,想了一下至少需要五篇才能说清楚。

如果觉得笔者写得还可以的话,请大家多关注,给个支持!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335