别看Spring现在玩的这么花,其实它的“筹码”就两个,“容器”和“bean定义”。

只有先把bean定义注册到容器里,后续的一切可能才有可能成为可能。

所以在进阶的路上如果要想走的顺畅些,彻底搞清楚bean定义注册的所有细节至关重要。

毕竟这是万里长征的第一步。有句话怎么说来着,“勿在浮沙筑高台”。

Spring步入注解和Java配置的时代也有些时日了。而且也旗帜鲜明的表达了bean的注册方法。

这不,就是这个接口,AnnotationConfigRegistry,如下图01:

再来看下这个接口的名字,有三个单词组成,Annotation、Config、Registry。

第一个表示注解,第二个表示Java配置,第三个表示注册。

合起来的意思可以理解为,基于注解和Java配置的bean定义注册。当然,这是我猜的,哈哈哈。

这是一个很牛X的接口,理由见下图02:

纳尼,所有的容器类都实现了它。

不管是web的、非web的,传统Spring的、SpringBoot的,响应式的、Servlet的。

简直是老少通吃、妇孺皆宜啊。淡定、淡定。

这个接口的两个方法非常简约

一个是直接把一个类(Class<?>)进行注册。

一个是通过扫描指定的包(Package)里的类进行注册。

请注意我刚刚使用了“简约”而没有使用“简单”,因为简约往往并不等于简单,反而更多时候等于难。

讲了这么多,终于可以让今天的主角登场了,来来来,掌声响起来。

就是这两个类:

AnnotatedBeanDefinitionReader

ClassPathBeanDefinitionScanner

第一个类就是站在接口第一个方法register背后默默付出的。

第二个类就是负责搞定接口第二个方法scan后面所有事情的。

下面开始进行具体的讲解,只需要知道都干了什么即可,至于怎么干的,不需要了解。

看第一个类,如下图03:

第一个字段类型是BeanDefinitionRegistry,这是容器(或bean factory)会实现的接口。

用于把一个BeanDefinition(bean定义)注册到容器中,看下它的这个方法,如下图04:

编程新说注:对“bean定义”这个概念不清楚的,可以在文末查看本系列《品Spring》文章的头几篇。

刚刚应该看到在注册bean定义时需要一个bean名称(即beanName),因此该第二个字段发挥作用了。

它就是BeanNameGenerator。例如,有一个类是UserController,它上面标了注解@Controller("user")。

首先它会把注解的value属性作为名称,此时就是user啦。

如果没有指定value属性,就像这样@Controller,此时就是类的短名称且首字母小写,即userController。

这就是bean名称的生成策略,在实际开发中不就是这样的嘛。

第三个字段是ScopeMetadataResolver,是来决定bean实例的范围(即生命周期)的。

常见的生命周期有四种,PROTOTYPE(原型)、SINGLETON(单例)、REQUEST(请求)、SESSION(会话)。

就是通过检查类上有没有@Scope这个注解。如果有的话,就按指定的走,没有的话,就按单例走。

第四个字段是ConditionEvaluator,条件计算器,根据“条件”判断一个bean定义该不该被注册。

这可是SpringBoot自动配置(AutoConfiguration)的基石啊。

就是去检测类上有没有标@Conditional这个注解。如果没有的话,bean定义会被注册。

如果有的话,需要再去计算具体的“条件”,然后才能确定bean定义到底要不要注册。

哎呀,注册一个bean定义好麻烦啊,喘口气,继续吧。嘿嘿。

下面开始真正进入注册的方法,先看下方法的参数吧,如下图05:

方法共有5个参数,只有第一个是必须的,后面的都可以为空。

第一个参数,annotatedClass,是Class<?>,表示要被注册的类。

第二个参数,instanceSupplier,是一个函数式接口,Supplier<T>,可以提供这个bean的实例对象,这样就不再需要通过反射调用构造函数了。

第三个参数,name,bean名称,如果传的话就不用再生成了。

第四个参数,qualifiers,是一组用作限定修饰符的注解,Class<? extends Annotation>[]。

第五个参数,definitionCustomizers,是一组可以自定义bean定义的接口,BeanDefinitionCustomizer。

整个处理过程分为九步,如下图06:

第一步,先把类转变为bean定义,即把Class<?>转变为BeanDefinition。具体是AnnotatedGenericBeanDefinition这个类。

第二步,使用条件计算器来确定是否要注册这个bean定义。

第三步,确定这个bean的生命周期。

第四步,确定这个bean的名称。

第五步,处理定义的公共注解信息。如下图07:

就是@Lazy、@Primary、@DependsOn、@Role、@Description这五个注解。

从类上分别获取这些注解,然后从注解中读出需要的信息,再把这些信息设置到bean定义中。

第六步,处理限定修饰符,就是@Primary、@Lazy、@Qualifier这三个注解。

这个几个注解是从方法参数传入的,上一步的注解是从类上读取的,它们不重复也不冲突。

编程新说注:这些注解的含义和用法,这里就不说了,毕竟这是“追求深度”的文章。

第七步,应用bean定义自定义器,对bean定义进行一些自定义。

第八步,根据bean的生命周期,使用AOP技术为该bean定义生成代理。

第九步,把这个bean定义注册到容器中。

这就是一个bean定义的完整注册过程。妈呀,让我歇会儿。

编程新说注:第二个类注册bean定义的整体逻辑和第一个类完全一样。只是获取bean定义的方式不同。

下面看第二个类,如下图08:

首先可以看到,它扫描的都是jar包中的.class文件。

然后还有两个过滤器集合,决定哪些被排除、哪些被包含。

当然,类中也给出了默认情况下包含的,如下图09:

第一,@Component注解以及用它定义的其它注解,如@Configuration等。

第二,JSR-250里面的@javax.annotation.ManagedBean注解。

第三,JSR-330里面的@javax.inject.Named注解。

标了这三个注解的类都会被注册,第一个注解是Spring的,后两个是Java的。

默认情况下,排除过滤器没有指定,也就是不进行任何显式的排除。

具体收集bean定义的过程,分为七步,如下图10:

第一步,拼接资源路径,形式就是这样classpath*:org/cnt/ts/**/*.class。

它表示搜索类路径下所有的jar包里,以org/cnt/ts开头的包及其子包里的所有.class文件。

第二步,找出上一步中的那些.class文件,并把它们转化为资源,即Resource类。

第三步,使用ASM框架逐个读取这些资源(其实就是字节码文件啦)。

第四步,应用过滤器和条件计算器,来确定这个bean定义是否要被注册。

如下图11:

第五步,使用从字节码中读出的内容来构建BeanDefinition,使用的是ScannedGenericBeanDefinition这个类。

第六步,确认下这个类是否符合要求,如下图12:

一共有三项检查:

第一,必须是独立的。可以是顶级类(非内部类),可以是静态内部类(即static class)。

第二,必须是具体的,即非抽象的。

第三,如果类是抽象的,它必须包含一个标有@Lookup注解的方法,来指定一个具体的bean。

第七步,收集好这个bean定义。

这些bean定义抽取好后,剩下的处理就和第一个类一样了。

如下图13:

也是确定生命周期,生成bean名称,处理定义的公共注解信息,根据生命周期生成代理,最后注册到容器中。

最后声明一点

以上两个类并不处理@Bean这个注解注册的bean定义,也不处理由@Import注解引入的bean定义。

哪谁处理呢?后续文章见。

>>> 品Spring系列文章 <<<

品Spring:帝国的基石

品Spring:bean定义上梁山

品Spring:实现bean定义时采用的“先进生产力”

品Spring:注解终于“成功上位”

品Spring:能工巧匠们对注解的“加持”

品Spring:SpringBoot和Spring到底有没有本质的不同?

作者是工作超过10年的码农,现在任架构师。喜欢研究技术,崇尚简单快乐。追求以通俗易懂的语言解说技术,希望所有的读者都能看懂并记住。下面是公众号和知识星球的二维码,欢迎关注!

       

品Spring:负责bean定义注册的两个“排头兵”的更多相关文章

  1. 品Spring:bean定义上梁山

    认真阅读,收获满满,向智慧又迈进一步... 技术不枯燥,先来点闲聊 先说点好事高兴一下.前段时间看新闻说,我国正式的空间站建设已在进行当中.下半年,长征五号B运载火箭将在海南文昌航天发射场择机将空间站 ...

  2. 品Spring:SpringBoot轻松取胜bean定义注册的“第一阶段”

    上一篇文章强调了bean定义注册占Spring应用的半壁江山.而且详细介绍了两个重量级的注册bean定义的类. 今天就以SpringBoot为例,来看看整个SpringBoot应用的bean定义是如何 ...

  3. 品Spring:SpringBoot发起bean定义注册的“二次攻坚战”

    上一篇文章整体非常轻松,因为在容器启动前,只注册了一个bean定义,就是SpringBoot的主类. OK,今天接着从容器的启动入手,找出剩余所有的bean定义的注册过程. 具体细节肯定会颇为复杂,同 ...

  4. 品Spring:bean工厂后处理器的调用规则

    上一篇文章介绍了对@Configuration类的处理逻辑,这些逻辑都写在ConfigurationClassPostProcessor类中. 这个类不仅是一个“bean工厂后处理器”,还是一个“be ...

  5. Spring的Bean定义

    以下内容引用自http://wiki.jikexueyuan.com/project/spring/bean-definition.html: Bean定义 被称作bean的对象是构成应用程序的支柱也 ...

  6. Spring XML Bean 定义的加载和注册

    前言 本篇文章主要介绍 Spring IoC 容器怎么加载 bean 的定义元信息. 下图是一个大致的流程图: 第一次画图,画的有点烂.

  7. Spring中 bean定义的parent属性机制的实现分析

    在XML中配置bean元素的时候,我们常常要用到parent属性,这个用起来很方便就可以让一个bean获得parent的所有属性 在spring中,这种机制是如何实现的?     对于这种情况 tra ...

  8. 【Spring】bean动态注册到spring

    /* * http://412887952-qq-com.iteye.com/blog/2348445 * http://www.jb51.net/article/106558.htm * https ...

  9. 品Spring:详细解说bean后处理器

    一个小小的里程碑 首先感谢能看到本文的朋友,感谢你的一路陪伴. 如果每篇都认真看的话,会发现本系列以bean定义作为切入点,先是详细解说了什么是bean定义,接着又强调了bean定义为什么如此重要. ...

随机推荐

  1. Python -二叉树 创建与遍历算法(很详细)

    树表示由边连接的节点.它是一个非线性的数据结构.它具有以下特性. 一个节点被标记为根节点. 除根节点之外的每个节点都与一个父节点关联. 每个节点可以有一个arbiatry编号的chid节点. 我们使用 ...

  2. 如何编写高质量的 JS 函数(2) -- 命名/注释/鲁棒篇

    本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/sd2oX0Z_cMY8_GvFg8pO4Q作者:杨昆 上篇<如何编写高质量的 JS 函数 ...

  3. Markdown写作入门

    什么是Markdown格式 Markdown格式是一种可用普通文本编辑器编写的标记语言,使用者能够通过简单的标记语法,对自己所写文本内容进行简单的格式排版: 优点 语法简洁易学,且功能比纯文本强大: ...

  4. Linux shell 内部命令与外部命令有什么区别以及怎么辨别

    内部命令实际上是shell程序的一部分,其中包含的是一些比较简单的linux系统命令,这些命令由shell程序识别并在shell程序内部完成运行,通常在linux系统加载运行时shell就被加载并驻留 ...

  5. 学习HTML之后的感受

    自从学习了HTML之后,感觉自己每天面对密密麻麻的代码,都有了一种密集恐惧症的感觉,作为一个计算机行业的小白,我十分渴望在计算机行业有所建树,以前计算机对我来说是一个神秘的领域.现在我正在努力进入这个 ...

  6. 学会spss就能找到数据分析工作吗

     大学课堂上学习了spss,老师也讲了很多知识,但是现在准备毕业了,我做的实习工作就是用业内的数据进行最新的行业研究.现在真正需要用到spss进行分析了,我却看不懂老板给的数据和分析要求,难道这就是理 ...

  7. c#中的委托01

    delegate 是表示对具有特定参数列表和返回类型的方法的引用的类型. 在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联. 你可以通过委托实例调用方法. 委托用于将方法作为参数 ...

  8. 2019NC#8

    题号 标题 已通过代码 题解/讨论 通过率 团队的状态 A All-one Matrices 点击查看 单调栈+前缀和 326/2017  通过 B Beauty Values 点击查看 进入讨论 8 ...

  9. Linux系统调用表(x86_64)

    内核版本 Linux 4.7 Note:64位系统中,不再使用int 0x80来进行系统调用,取而代之的是syscall指令 %rax System call %rdi %rsi %rdx %r10 ...

  10. odoo12之应用:一、双因子验证(Two-factor authentication, 2FA)(HOTP,TOTP)附源码

    前言 双因子认证:双因子认证(2FA)是指结合密码以及实物(信用卡.SMS手机.令牌或指纹等生物标志)两种条件对用户进行认证的方法.--百度百科 跟我一样"老"的网瘾少年想必一定见 ...