前面几篇文章已经学习了官网中的1.2,1.3,1.4三小结,主要是容器,Bean的实例化及Bean之间的依赖关系等。这篇文章,我们继续官网的学习,主要是BeanDefinition的相关知识,这是Spring中非常基础的一块内容,也是我们阅读源码的基石。本文主要涉及到官网中的1.31.5中的一些补充知识。同时为我们1.7小节中BeanDefinition的合并做一些铺垫

BeanDefinition是什么?

我们先看官网上是怎么解释的:

从上文中,我们可以得出以下几点结论:

  1. BeanDefinition包含了我们对bean做的配置,比如XML<bean/>标签的形式进行的配置
  2. 换而言之,Spring将我们对bean的定义信息进行了抽象,抽象后的实体就是BeanDefinition,并且Spring会以此作为标准来对Bean进行创建
  3. BeanDefinition包含以下元数据:
    • 一个全限定类名,通常来说,就是对应的bean的全限定类名。
    • bean的行为配置元素,这些元素展示了这个bean在容器中是如何工作的包括scope(域,我们文末有简单介绍),lifecycle callbacks(生命周期回调,下篇文章介绍)等等
    • 这个bean的依赖信息
    • 一些其他配置信息,比如我们配置了一个连接池对象,那么我们还会配置它的池子大小,最大连接数等等

在这里,我们来比较下,正常的创建一个bean,跟Spring通过抽象出一个BeanDefinition来创建bean有什么区别:

正常的创建一个java bean:

Spring通过BeanDefinition来创建bean:

通过上面的比较,我们可以发现,相比于正常的对象的创建过程,Spring对其管理的bean没有直接采用new的方式,而是先通过解析配置数据以及根据对象本身的一些定义而获取其对应的beandefinition,并将这个beandefinition作为之后创建这个bean的依据。同时Spring在这个过程中提供了一些扩展点,例如我们在图中所提到了BeanfactoryProcessor。这些大家先作为了解,之后在源码阶段我们再分析。

BeanDefinition的方法分析

这里对于每个字段我只保留了一个方法,只要知道了字段的含义,方法的含义我们自然就知道了

// 获取父BeanDefinition,主要用于合并,下节中会详细分析
String getParentName(); // 对于的bean的ClassName
void setBeanClassName(@Nullable String beanClassName); // Bean的作用域,不考虑web容器,主要两种,单例/原型,见官网中1.5内容
void setScope(@Nullable String scope); // 是否进行懒加载
void setLazyInit(boolean lazyInit); // 是否需要等待指定的bean创建完之后再创建
void setDependsOn(@Nullable String... dependsOn); // 是否作为自动注入的候选对象
void setAutowireCandidate(boolean autowireCandidate); // 是否作为主选的bean
void setPrimary(boolean primary); // 创建这个bean的类的名称
void setFactoryBeanName(@Nullable String factoryBeanName); // 创建这个bean的方法的名称
void setFactoryMethodName(@Nullable String factoryMethodName); // 构造函数的参数
ConstructorArgumentValues getConstructorArgumentValues(); // setter方法的参数
MutablePropertyValues getPropertyValues(); // 生命周期回调方法,在bean完成属性注入后调用
void setInitMethodName(@Nullable String initMethodName); // 生命周期回调方法,在bean被销毁时调用
void setDestroyMethodName(@Nullable String destroyMethodName); // Spring可以对bd设置不同的角色,了解即可,不重要
// 用户定义 int ROLE_APPLICATION = 0;
// 某些复杂的配置 int ROLE_SUPPORT = 1;
// 完全内部使用 int ROLE_INFRASTRUCTURE = 2;
void setRole(int role); // bean的描述,没有什么实际含义
void setDescription(@Nullable String description); // 根据scope判断是否是单例
boolean isSingleton(); // 根据scope判断是否是原型
boolean isPrototype(); // 跟合并beanDefinition相关,如果是abstract,说明会被作为一个父beanDefinition,不用提供class属性
boolean isAbstract(); // bean的源描述,没有什么实际含义
String getResourceDescription(); // cglib代理前的BeanDefinition
BeanDefinition getOriginatingBeanDefinition();

BeanDefinition的继承关系

类图如下:

1.BeanDefinition继承的接口

  • org.springframework.core.AttributeAccessor

先来看接口上标注的这段java doc

Interface defining a generic contract for attaching and accessing metadata to/from arbitrary objects.

翻译下来就是:

这个接口为从其它任意类中获取或设置元数据提供了一个通用的规范。

其实这就是访问者模式的一种体现,采用这方方法,我们可以将数据接口操作方法进行分离。

我们再来看这个接口中定义的方法:

void setAttribute(String name, @Nullable Object value);

Object getAttribute(String name);

Object removeAttribute(String name);

boolean hasAttribute(String name);

String[] attributeNames();

就是提供了一个获取属性跟设置属性的方法

那么现在问题来了,在我们整个BeanDefiniton体系中,这个被操作的数据结构在哪呢?不要急,在后文中的AbstractBeanDefinition会介绍。

  • org.springframework.beans.BeanMetadataElement

我们还是先看java doc:

Interface to be implemented by bean metadata elements that carry a configuration source object.

翻译:这个接口提供了一个方法去获取配置源对象,其实就是我们的原文件。

这个接口只提供了一个方法:

@Nullable
Object getSource();

我们可以理解为,当我们通过注解的方式定义了一个IndexService时,那么此时的IndexService对应的BeanDefinition通过getSource方法返回的就是IndexService.class这个文件对应的一个File对象。

如果我们通过@Bean方式定义了一个IndexService的话,那么此时的source是被@Bean注解所标注的一个Mehthod对象。

2.AbstractBeanDefinition

AbstractBeanDefinition的继承关系

先看一下类图:

  • org.springframework.core.AttributeAccessorSupport

可以看到这个类实现了AttributeAccerror接口,我们在上文中已经提到过,AttributeAccerror采用了访问者的涉及模式,将数据结构操作方法进行了分离,数据结构在哪呢?就在AttributeAccessorSupport这个类中,我们看下它的代码:

public abstract class AttributeAccessorSupport implements AttributeAccessor, Serializable {
/** Map with String keys and Object values. */
private final Map<String, Object> attributes = new LinkedHashMap<>(); @Override
public void setAttribute(String name, @Nullable Object value) {
Assert.notNull(name, "Name must not be null");
if (value != null) {
this.attributes.put(name, value);
}
else {
removeAttribute(name);
}
}
......省略下面的代码

可以看到,在这个类中,维护了一个map,这就是BeanDefinition体系中,通过访问者模式所有操作的数据对象。

  • org.springframework.beans.BeanMetadataAttributeAccessor

这个类主要就是对我们上面的map中的数据操作做了更深一层的封装,我们就看其中的两个方法:

public void addMetadataAttribute(BeanMetadataAttribute attribute) {
super.setAttribute(attribute.getName(), attribute);
}
public BeanMetadataAttribute getMetadataAttribute(String name) {
return (BeanMetadataAttribute) super.getAttribute(name);
}

可以发现,它只是将属性统一封装成了一个BeanMetadataAttribute,然后就调用了父类的方法,将其放入到map中。

我们的AbstractBeanDefinition通过继承了BeanMetadataAttributeAccessor这个类,可以对BeanDefinition中的属性进行操作。这里说的属性仅仅指的是BeanDefinition中的一个map,而不是它的其它字段。

为什么需要AbstractBeanDefinition?

对比BeanDefinition的源码我们可以发现,AbstractBeanDefinitionBeanDefinition的大部分方法做了实现(没有实现parentName相关方法)。同时定义了一系列的常量及默认字段。这是因为BeanDefinition接口过于顶层,如果我们依赖BeanDefinition这个接口直接去创建其实现类的话过于麻烦,所以通过AbstractBeanDefinition做了一个下沉,并给很多属性赋了默认值,例如:

// 默认情况不是懒加载的
private boolean lazyInit = false;
// 默认情况不采用自动注入
private int autowireMode = AUTOWIRE_NO;
// 默认情况作为自动注入的候选bean
private boolean autowireCandidate = true;
// 默认情况不作为优先使用的bean
private boolean primary = false;
........

这样可以方便我们创建其子类,如我们接下来要讲的:ChildBeanDefinition,RootBeanDefinition等等

3.AbstractBeanDefinition的三个子类

GenericBeanDefinition

  • 替代了原来的ChildBeanDefinition,比起ChildBeanDefinition更为灵活,ChildBeanDefinition在实例化的时候必须要指定一个parentName,而GenericBeanDefinition不需要。我们通过注解配置的bean以及我们的配置类(除@Bena外)的BeanDefiniton类型都是GenericBeanDefinition

ChildBeanDefinition

  • 现在已经被GenericBeanDefinition所替代了。我在5.1.x版本没有找到使用这个类的代码。

RootBeanDefinition

  • Spring在启动时会实例化几个初始化的BeanDefinition,这几个BeanDefinition的类型都为RootBeanDefinition
  • Spring在合并BeanDefinition返回的都是RootBeanDefinition
  • 我们通过@Bean注解配置的bean,解析出来的BeanDefinition都是RootBeanDefinition(实际上是其子类ConfigurationClassBeanDefinition

4.AnnotatedBeanDefinition

这个接口继承了我们的BeanDefinition接口,我们查看其源码可以发现:

AnnotationMetadata getMetadata();

@Nullable
MethodMetadata getFactoryMethodMetadata();

这个接口相比于BeanDefinition, 仅仅多提供了两个方法

  • getMetadata(),主要用于获取注解元素据。从接口的命名上我们也能看出,这类主要用于保存通过注解方式定义的bean所对应的BeanDefinition。所以它多提供了一个关于获取注解信息的方法

    • getFactoryMethodMetadata(),这个方法跟我们的@Bean注解相关。当我们在一个配置类中使用了@Bean注解时,被@Bean注解标记的方法,就被解析成了FactoryMethodMetadata

5.AnnotatedBeanDefinition的三个实现类

AnnotatedGenericBeanDefinition

  • 通过形如下面的API注册的bean都是AnnotatedGenericBeanDefinition
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
ac.register(Config.class);
}

这里的config对象,最后在Spring容器中就是一个AnnotatedGenericBeanDefinition

  • 通过@Import注解导入的类,最后都是解析为AnnotatedGenericBeanDefinition

ScannedGenericBeanDefinition

  • 都过注解扫描的类,如@Service,@Compent等方式配置的Bean都是ScannedGenericBeanDefinition

ConfigurationClassBeanDefinition

  • 通过@Bean的方式配置的Bean为ConfigurationClassBeanDefinition

最后,我们还剩一个ClassDerivedBeanDefinition,这个类是跟kotlin相关的类,一般用不到,笔者也不熟,这里就不管了!

总结

至此,我们算完成了BeanDefinition部分的学习,在下一节中,我将继续跟大家一起学习BeanDefinition合并的相关知识。这篇文章中,主要学习了

  1. 什么是BeanDefinition,总结起来就是一句话,Spring创建bean时的建模对象。
  2. BeanDefinition的具体使用的子类,以及Spring在哪些地方使用到了它们。这部分内容在后面的学习中很重要,画图总结如下:

1.5小结内容的补充

单例

一个单例的bean意味着,这个bean只会容器创建一次。在创建后,容器中的每个地方使用的都是同一个bean对象。这里用Spring官网上的一个原图:



在上面图片的例子中,accountDao在被其它三个bean引用,这三个引用指向的都是同一个bean。

在默认情况下,Spring中bean的默认域就是单例的。分XML跟注解两种配置方式:

<!--即使配置singleton也是单例的,这是Spring的默认配置-->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
@Component
// 这里配置singleton,默认就是singleton
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class LuBanService{ }

原型

一个原型的bean意味着,每次我们使用时都会重新创建这个bean。

在上面图片的例子中,accountDao在被其它三个bean引用,这三个引用指向的都是一个新建的bean。

两种配置方式:

<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
@Component
// 这里配置prototype
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class LuBanService{ }

Spring官网阅读(四)BeanDefinition(上)的更多相关文章

  1. Spring官网阅读 | 总结篇

    接近用了4个多月的时间,完成了整个<Spring官网阅读>系列的文章,本文主要对本系列所有的文章做一个总结,同时也将所有的目录汇总成一篇文章方便各位读者来阅读. 下面这张图是我整个的写作大 ...

  2. Spring官网阅读(十七)Spring中的数据校验

    文章目录 Java中的数据校验 Bean Validation(JSR 380) 使用示例 Spring对Bean Validation的支持 Spring中的Validator 接口定义 UML类图 ...

  3. Spring官网阅读(十六)Spring中的数据绑定

    文章目录 DataBinder UML类图 使用示例 源码分析 bind方法 doBind方法 applyPropertyValues方法 获取一个属性访问器 通过属性访问器直接set属性值 1.se ...

  4. Spring官网阅读(十八)Spring中的AOP

    文章目录 什么是AOP AOP中的核心概念 切面 连接点 通知 切点 引入 目标对象 代理对象 织入 Spring中如何使用AOP 1.开启AOP 2.申明切面 3.申明切点 切点表达式 excecu ...

  5. Spring官网阅读(三)自动注入

    上篇文章我们已经学习了1.4小结中关于依赖注入跟方法注入的内容.这篇文章我们继续学习这结中的其他内容,顺便解决下我们上篇文章留下来的一个问题-----注入模型. 文章目录 前言: 自动注入: 自动注入 ...

  6. Spring官网阅读(一)容器及实例化

    从今天开始,我们一起过一遍Spring的官网,一边读,一边结合在路神课堂上学习的知识,讲一讲自己的理解.不管是之前关于动态代理的文章,还是读Spring的官网,都是为了之后对Spring的源码做更全面 ...

  7. Spring官网阅读(二)(依赖注入及方法注入)

    上篇文章我们学习了官网中的1.2,1.3两小节,主要是涉及了容器,以及Spring实例化对象的一些知识.这篇文章我们继续学习Spring官网,主要是针对1.4小节,主要涉及到Spring的依赖注入.虽 ...

  8. Spring官网阅读(九)Spring中Bean的生命周期(上)

    文章目录 生命周期回调 1.Bean初始化回调 2.Bean销毁回调 3.配置默认的初始化及销毁方法 4.执行顺序 5.容器启动或停止回调 Lifecycle 接口 LifecycleProcesso ...

  9. Spring官网阅读(十一)ApplicationContext详细介绍(上)

    文章目录 ApplicationContext 1.ApplicationContext的继承关系 2.ApplicationContext的功能 Spring中的国际化(MessageSource) ...

随机推荐

  1. 自己写的一个HTML的小网页

    上次在上直播课的时候,教员提到了html这种标记语言.自己就在W3school上面学了一点点关于html的一些皮毛,自己动手写了一个小网页,同时自己对CTF这一块比较感兴趣,但是自己还是一个干干净净的 ...

  2. Java 基础讲解

    Hello,老同学们,又见面啦,新同学们,你们好哦! 在看完本人的<数据结构与算法>专栏的博文的老同学,恭喜你们在学习本专栏时,你们将会发现好多知识点都讲解过,都易于理解,那么,没看过的同 ...

  3. Unity 游戏框架搭建 2019 (三十二、三十三) 类的命名 & 代码文件命名

    昨天我们完成了第八个示例的第二个 MenuItem 菜单顺序的调整. 我们今天再往下接着调整. 我们来看下接下来的 MenuItem 代码如下: [MenuItem("QFramework/ ...

  4. python 基础篇 模块化

    在做项目的时候,虽然你不可能把全世界的代码都放到一个文件夹下,但是类似模块化的思想还是要有的--那就是以项目的根目录作为最基本的目录,所有的模块调用,都要通过根目录一层层向下索引的方式来 import ...

  5. Fiddler抓取抖音视频

    目录 工具 Fiddler配置 手机端配置 工具 Android 或 ios手机均可 Fiddler 下载地址:https://www.telerik.com/fiddler Windows 操作系统 ...

  6. CentOS6.5环境下搭建Apache httpd服务器

    前期准备: 1.CentOS6.5虚拟系统: 2.CentOS6.5 IP地址:192.168.2.99 3.虚拟系统能上外网 下面我们就开始配置: 第一步:安装 首先我们需要把Apache http ...

  7. 理解RESTful API

    近日妹子向我求助RESTful API到底是个什么东西.原因是她们公司一个新启动的项目因为RESTful API起了争执.服务端同学坚持要用RESTful API,而前端同学则认为服务端用RESTfu ...

  8. C#线程学习笔记

    本笔记摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/18/Thread.html,记录一下学习,方便后面资料查找 一.线程的介绍 进程(Proce ...

  9. 关于JavaEE中Spring模块的学习!

    七大模块,如下: 1. Spring Core: Core封装包是框架的最基础部分,提供IOC和依赖注入特性.这里的基础概念是BeanFactory,它提供对Factory模式的经典实现来消除对程序性 ...

  10. pytorch实现MLP并在MNIST数据集上验证

    写在前面 由于MLP的实现框架已经非常完善,网上搜到的代码大都大同小异,而且MLP的实现是deeplearning学习过程中较为基础的一个实验.因此完全可以找一份源码以参考,重点在于照着源码手敲一遍, ...