前几篇文章写了官网中的 1.2,1.3,1.4 三小节,主要是容器,Bean 的实例化及 Bean 之间的依赖关系。这篇文章继续学习官网,主要是 BeanDefinition 的相关知识,这是 Spring 中非常基础的一块内容,也是我们阅读源码的基石。本文主要涉及到官网中的 1.3 和 1.5 中的一些补充知识,同时为 1.7 小节中的 BeanDefinition 的合并做一些铺垫。
BeanDefinition 是什么?
我们先看官网上是怎么解释的:
从上文中我们可以得出几点结论:
- BeanDefinition 包含了我们对 bean 做的配置,比如 XML<bean/>标签的形式进行的配置
- Spring 将我们对 bean 的定义信息进行了抽象,抽象后的实体就是 BeanDefinition,并且 Spring 会以此作为标准对 bean 进行创建。
- BeanDefinition 包含以下元数据:
- 一个全限顶类名,通常来说,就是对应 bean 的全限定类名。
- bean 的行为配置元素,这些元素展示了这个 bean 在容器中是如何工作的,包括 scope,lifecycle callbacks(生命周期回调)等等。
- 这个 bean 的依赖信息。
- 一些其他配置信息,比如我们配置了一个连接池的对象,那么我们还会配置它池子的大小,最大连接数等等。
在这里我们来比较下,正常的创建一个 bean,和 Spring 通过抽象出一个 BeanDefinition 来创建 bean 有什么区别。
正常的创建一个 Java bean:
Spring 通过 BeanDefinition 创建 bean:
经过上面的比较我们发现,相比于正常创建对象的过程,Spring 对其管理的 bean 没有直接采用 new 的方式,而是先通过解析配置数据以及根据对象本身的一些定义而获取其对应的 beanDefinition,并将这个 beanDefinition 作为之后创建这个 bean 的一句,同时 Spring 在这个过程中提供了一些扩展点,例如我们在途中所提到的 BeanFactroyPostProcessor,这些之后源码在进行分析
BeanDefinition 的方法分析:
这里对于每个字段我只保留了一个方法,只有知道了字段的含义,方法的含义我们自然就知道了
org.springframework.beans.factory.config.BeanDefinition
/**
* 返回这个 bean 定义的父定义的名称(如果有的话)。
*/
@Nullable
String getParentName();
/**
* 指定这个 bean 定义的 bean 类名.
* <p>可以在 bean 工厂后处理期间修改类名,
* 通常使用解析后的类名变体替换原始类名.
* @see #setParentName
* @see #setFactoryBeanName
* @see #setFactoryMethodName
*/
void setBeanClassName(@Nullable String beanClassName);
/**
* 覆盖此 bean 的目标范围,指定一个新的范围名称.
* Bean 的作用域,不考虑 web 容器,主要两种,单例/原型,见官网 1.5 内容
* @see #SCOPE_SINGLETON
* @see #SCOPE_PROTOTYPE
*/
void setScope(@Nullable String scope);
/**
* 设置是否延迟初始化这个 bean。(是否懒加载)
* <p>I 如果{@code false},bean 将在启动时被实例化
* 执行单例急切初始化的工厂.
*/
void setLazyInit(boolean lazyInit);
/**
* 是否需要等待指定的 bean 创建完之后再创建
*/
void setDependsOn(@Nullable String... dependsOn);
/**
* 设置这个 bean 是否可以自动加载到其他 bean 中。
* <p>注意,这个标志被设计为只影响基于类型的自动装配。
* 它不影响按名称的显式引用,甚至可以解析名称
* 如果指定的 bean 没有标记为自动装配候选对象,
* 因此,如果名称匹配,按名称自动装配仍然会注入一个 bean.
* (是否作为自动注入的候选对象)
*/
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 的继承关系:
- BeanDefinition 继承的接口:
- org.springframework.core.AttributeAccessor
先来看接口上标志的这段 Java doc
Interface defining a generic contract for attaching and accessing metadata to/from arbitrary objects.
翻译下来就是:
这个接口为其他任意类中获取或设置元数据提供了一个通用的规范。
其实这就是访问者模式的一种体现,采用这种方法,我们可以将数据接口和操作方法进行分离。
我们再来看这个接口定义的方法:
就是提供了一个获取属性和设置属性的方法
那么现在问题来了,在我们整个 BeanDefinition 体现中,这个被操作的数据结构在哪里?在后文 AbstractBeanDefinition 会介绍。
- org.springframework.beans.BeanMetadataElement
我们还是先看 Java doc:
Interface to be implemented by bean metadata elements
that carry a configuration source object.
翻译:这个接口提供了一个方法去获取配置源对象,其实就是我们的原文件
这个接口只提供了一个方法:
/**
* Return the configuration source {@code Object} for this metadata element
* (may be {@code null}).
*/
@Nullable
Object getSource();
我们可以理解为,当我们通过注解的方式定义了一个 IndexService 时,那么此时的 IndexService 对应的 BeanDefinition 通过 getSource 方法返回的就是 IndexService.class 这个文件对应的一个 File 对象。
如果我们通过@Bean 方式定义了一个 IndexService 的话,那么此时的 source 是被@Bean 注解 s
所标注的一个 Method 对象。
- AbstractBeanDefinition:
AbstractBeanDefinition 的继承关系:
- org.springframework.core.AttributeAccessorSupport
可以看到这个类实现了 AttributeAccerror 接口,我们在上文中以及提到过,AttributeAccerror 采用了访问者的设计模式,将数据结构和操作方法进行了分离,数据结构在哪呢?就在 AttributeAccessorSupport 这个类中,我们看下它的代码:
可以看到这个类中,维护了一个 map,这就是 BeanDefinition 体现中,通过访问者模式所有操作的数据对象。
- org.springframework.beans.BeanMetadataAttributeAccessor
这个类主要是对我们上面的 map 中的数据操作做了更深一层的封装,我们就看其中的两个方法:
可以发现,它只是将数学统一封装成一个 BeanMetadataAttribute,然后调用了父类的方法,将其放入到 map 中。
我们的 AbstractBeanDefinition 通过继承了 BeanMetadataAttributeAccessor 这个类,可以对 BeanDefinition 中的属性进行操作。这里说的属性仅仅指定是 BeanDefinition 中的一个 map,而不是其它字段
为什么需要 AbstractBeanDefinition?
对比 BeanDefinition 的源码可以发现,AbstractBeanDefinition 对 BeanDefinition 的大部分方法做了实现(没有实现 parentName 相关方法)。同时定义了一系列的常量及默认字段。这是因为 BeanDefinition 接口过于底层,如果我们一来 BeanDefinition 这个接口直接去创建其实现类的话过于麻烦,所以 AbstractBeanDefinition 做了一个下沉,并给很多属性赋了默认值,例如:
这样可以方便我们创建其子类,入我们接下来要将的:
RootBeanDefinition,ChildBeanDefinition 等等
- AbstractBeanDefinition 的三个子类
GenericBeanDefinition:
- 替代了原来的 ChildBeanDefinition,比起 ChildBeanDefinition 更为灵活,ChildBeanDefinition 在实例化的时候必须要指定一个 parentName,而 GenericBeanDefinition 不需要。我们通过注解配置的 bean 以及我们的配置类(除@Bean 外)的 BeanDefinition 类型都是 GenericBeanDefinition。
ChildBeanDefinition:
- 现在已经被 GenericBeanDefinition 替代了,我在 5.1.x 版本没有找到使用这个类的代码
RootBeanDefinition:
- Spring 在启动时会实例化几个初始化的 BeanDefinition,这几个 BeanDefinition 的类型都为 RootBeanDefinition。
- Spring 在合并 BeanDefinition 返回的都是 RootBeanDefinition。
- 我们通过@Bean 注解配置的 bean,解析出来的 BeanDefinition 都是 RootBeanDefinition(实际上是其子类 ConfigurationClassBeanDefinition)
- AnnotatedBeanDefinition
这个接口继承了我们的 BeanDefinition 接口,我们查看源码可以发现:
这个接口相比于 BeanDefinition,仅仅多提供了两个方法
- getMetadata(),主要用于获取注解元数据,从接口的命名也能看出,这类主要用来保存通过注解方式定义的 bean 所对应的 BeanDefinition。所以它多提供了一个关于获取注解信息的方法
- getFactoryMethodMetadata(),这个方法和@Bean 注解相关。当我们在一个配置类中使用了@Bean 注解时,被@Bean 注解标记的方法,就被解析成了 FactoryMethodMetadata.
- 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 都是
ConfigurationClassBeanDefinition:
- 通过@Bean 的方式配置的 bean 为 ConfigurationClassBeanDefinition
最后,我们还剩下一个 ClassDerivedBeanDefinition,这个类是和 kotlin 相关的子类,一般用不到,我也不熟~!!
总结
至此,这里算是写完了部分 BeanDefinition,在下一篇中将继续写 BeanDefinition 合并相关的知识。这篇文章,主要讲了:
- 什么是 BeanDefinition,总结起来就是一句话,Spring 创建 bean 时的建模对象。
- 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 IndexService{
}
原型:
在上面图片例子中,accountDao 在被其它三个 bean 引用,这三个引用指向的都是一个新建的 bean。
两种配置方式:
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
@Component
// 这里配置 prototype
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class IndexService{
}