前面几篇文章已经学习了官网中的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. springboot项目war包部署及出现的问题Failed to bind properties under 'mybatis.configuration.mapped-statements[0].

    1.修改pom文件 修改打包方式 为war: 添加tomcat使用范围,provided的意思即在发布的时候有外部提供,内置的tomcat就不会打包进去 <groupId>com.scho ...

  2. 【python实现卷积神经网络】激活层实现

    代码来源:https://github.com/eriklindernoren/ML-From-Scratch 卷积神经网络中卷积层Conv2D(带stride.padding)的具体实现:https ...

  3. GeoGebra函数使用

    分段函数使用 输入指令: If(x < -2, x, -2 < x < 2, x², x > 2, x)

  4. JAVA—线程(Thread)

    1.线程的状态有哪些 我记得在操作系统原理的书上有一张具体的图,暂时找不到书... new:新建状态,被创建出来后未启动时的线程状态. runnable:就绪状态,表示可以运行. blocked:阻塞 ...

  5. BUG 测试计划

       性能追求 目前状况 测试标准 APP平稳运行,无crush现象   快速下拉翻页时,崩溃退出     要求多人使用,均流畅无异常退出方可               页面的放大缩小不会造成页面显 ...

  6. stand up meeting 12/22/2015 && 用户体验收录

    part 组员                工作              工作耗时/h 明日计划 工作耗时/h    UI 冯晓云  完善页面切换,尝试子页面设计    4  完善页面切换和子页面 ...

  7. B - How Many Equations Can You Find dfs

    Now give you an string which only contains 0, 1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9.You are asked to add the sig ...

  8. CVE 2019-0708 漏洞复现+

    PART 1 参考链接:https://blog.csdn.net/qq_42184699/article/details/90754333 漏洞介绍: 当未经身份验证的攻击者使用 RDP 连接到目标 ...

  9. IDEA惊天bug:进程已结束,退出代码-1073741819 (0xC0000005)

    由于昨天要写的文章没有写完,于是今天早上我四点半就"自然醒"了,心里面有事,睡觉也不安稳.洗漱完毕后,我打开电脑,正襟危坐,摆出一副要干架的态势,不能再拖了. 要写的文章中涉及到一 ...

  10. Java 基础之详解 Java 反射机制

    一.什么是 Java 的反射机制?   反射(Reflection)是Java的高级特性之一,是框架实现的基础,定义:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法: ...