深入了解 Spring 篇之 BeanDefinition 结构

简介: 通过对 BeanDefinition 结构的介绍,可以对整个 IOC 框架有进一步熟悉掌握,使用 spring 会更加灵活;

一. 概述

BeanDefinition 是定义对 Bean 的接口是 spring 容器中最重要的一个接口,spring 围绕这个接口进行对象的创建以及对象中的属性注入。

网络异常,图片无法展示
|

  • AbstractBeanDefinition 是整个 BeanDefinition 接口的抽象类,也是具体实现类的基础类,里面的属性内容基本含盖了 spring 的 IOC 的功能特点;
  • RootBeanDefinition 在 spring 初始化 bean 实例过程所使用的复合对象;
  • ScannedGenericBeanDefinition
  • ConfigurationClassBeanDefinition 针对 classpath 解析用途
  • AnnotatedGenericBeanDefinition
  • GenericBeanDefinition

只是简单对其进行介绍其用途,但下文并不是对照本宣科的对其进行详细介绍;而是从零开始设计一套 IOC 的角度出发进行解读 BeanDefinition 结构,这样子更加对其原理以及设计思想更加了解,后续使用时就不再是陌生的;

相关 spring 源代码是 5.1.2.RELEASE 版本;

二.  对象实例化

2.1 构建对象

在常规开发中,如果要创建对象,有如下方式:

  • 通过{静态}工厂方法进行创建,例如:
publicclassFactory{
public {static} Object<?>createBean(){
//....  }
}

复制代码

  • 通过构造方法进行创建,例如
publicclassTest {
publicstaticvoidmain(String [] args) throwsException {
Constructor<Person>constructor=Person.class.getConstructor();
Personman=constructor.newInstance();
System.out.println(man);
    }
publicstaticclassPerson{
privateStringname;
publicPerson() {
        }
@OverridepublicStringtoString() {
return"Person{"+"name='"+name+'\''+'}';
        }
    }
}

复制代码

  • 通过 Supplier 方式进行创建,例如:
Supplier<T>instanceSupplier=newSupplier(){
publicTget(){
//....  }
}

复制代码

  • 通过 cglib 方式进行创建增强版对象。

除 Supplier 外,工厂方法和构造方法都是通过反射调用 Method 对象进行对象创建。所以 spring 提供一个接口 InstantiationStrategy,其将调用 Method 对象进行对象创建的逻辑封装到 SimpleInstantiationStrategy 实现类里面;

那么 BeanDefinition 结构中就包含了上面的几个创建对象的内容;

publicabstractclassAbstractBeanDefinition {
privatevolatileObjectbeanClass;
/*** Supplier方式*/privateSupplier<?>instanceSupplier;
/*** 工厂方法进行创建*/privateStringfactoryBeanName;
privateStringfactoryMethodName;
}

复制代码

1. 无参数构建对象

既然提供如上方式进行对象创建,那么意味着会有优先级;那么我们就可以设计一个简单的流程(无参数的方法),对目标类型进行实例化对象,如图所示:

网络异常,图片无法展示
|

2. 有入参构建对象

在无参构建对象过程基础上,添加这么一个逻辑,【通过指定规则从中选出目标方法】,如下图;

网络异常,图片无法展示
|

指定规则如下:

匹配方法入参类型与入参对象类型的逻辑。如果都不匹配,意味着找不到合适的方法,直接报错;如果有匹配的方法有2个以上,那我们就需要决策了,具体需要哪种策略了;目前有两种策略,如下:
1. 宽松策略:优先选择第一个方法;
2. 严格策略:再次精细化类型匹配,根据类型的继承层级来判断,层级越低,说明类型关系越接近,则优先选择关系越接近的方法;
ps. 具体的实现在ArgumentsHolder类

复制代码

具体采用哪种策略,直接存放到 BeanDefinition 中;

publicabstractclassAbstractBeanDefinition {
/*** true代表是采用严格策略;* false代表是采用宽松策略*/privatebooleanlenientConstructorResolution=true;
}

复制代码

上面的过程中,通过反射来获取的方法列表,这里有两个可能性,是只获取 public 方法,还是获取所有的方法;

publicabstractclassAbstractBeanDefinition {
/*** true代表获取所有方法;* false代表是只获取public方法*/privatebooleannonPublicAccessAllowed=true;
}

复制代码

接下来介绍如何将 ConstructorArgumentValues 解析成 ArgumentsHolder 对象的;

接下来对入参解析进行介绍,

public abstract class AbstractBeanDefinition {  /**   * 构建对象的入参对象   */  private ConstructorArgumentValues constructorArgumentValues;}
public class ConstructorArgumentValues {  private Map<Integer/*参数位置*/, ValueHolder/*参数对象*/> indexedArgumentValues;  private List<ValueHolder> genericArgumentValues;}
public class ValueHolder{  /**   * 原始值对象   */  private Object value;  /**   * 是否已经转换过了,也就是意味着是否已经解析过了   */  private boolean converted = false;  /**   * 解析后的对象   */  private Object convertedValue;}
public class ArgumentsHolder {  public final Object[] arguments;}

复制代码

转换的流程如下:

网络异常,图片无法展示
|


阶段一:旧ConstructorArgumentValues对象转成新ConstructorArgumentValues对象
遍历旧ConstructorArgumentValues中的ValueHolder对象,经过spring解析得到新的ValueHolder对象
PS. 主要的实现代码BeanDefinitionValueResolver.resolveValueIfNecessary方法中。
值得注意的是,这里有用到EL表达式;所以,一些灵活得到入参对象的,可以通过EL表达式来抉择;
阶段二:新ConstructorArgumentValues对象解析成ArgumentsHolder
遍历方法的入参数组,通过位置角标获取对应的ValueHolder对象;
如果获取不到,则尝试在genericArgumentValues对象中查找;
找到的话,会根据是否已经转换(converted为true),来选择对于的属性值;
如果converted为true,则选择convertedValue属性对象
如果converted为false,则选择value经过TypeConverter类型转换类进行转换得到的对象;

复制代码

3. 入参解析缓存机制

经过 spring 创建的对象,并不会只有一次,所以为了提高第二次的创建对象,设计了缓存机制;

其缓存机制主要是为了减少查找目标构建对象的方法;至于入参是否有必要在解析,是根据 ConstructorArgumentValues 对象的 ValueHolder 对象中的 converted 是否为 false,只要有一个为 false,那就意味着有必要进行解析;

publicclassRootBeanDefinition {
/*** 为了避免并发实例同一个对象,需要一个锁来解决并发问题*/finalObjectconstructorArgumentLock=newObject();
/*** 存放第一次构建对象的方法*/ExecutableresolvedConstructorOrFactoryMethod;
/*** 第一次解析后,就会设置为true*/booleanconstructorArgumentsResolved=false;
/*** 存放解析后的入参对象*/Object[] resolvedConstructorArguments;
/*** 存放未解析的入参对象*/Object[] preparedConstructorArguments;
/*** 只缓存工厂方法,如果构建是通过工厂方法方式,* 那么该属性与resolvedConstructorOrFactoryMethod是同一个对象*/volatileMethodfactoryMethodToIntrospect;
}

复制代码

在有入参构建对象的逻辑图基础在添加缓存机制,如下图:

网络异常,图片无法展示
|

为了处理是否有必要解析入参这个场景,就需要在 ValueHolder、ArgumentsHolder 对象添加额外的属性,来存放解析前的对象;

publicclassValueHolder{
/*** 原始值对象*/privateObjectvalue;
/*** 是否已经转换过了,也就是意味着是否已经解析过了*/privatebooleanconverted=false;
/*** 解析后的对象*/privateObjectconvertedValue;
/*** 原始入参对象*/privateObjectsource;
}
publicclassArgumentsHolder {
/*** 原生对象*/publicfinalObject[] arguments;
/*** 解析后的入参对象集合*/publicfinalObject[] rawArguments;
/*** 未解析的入参对象*/publicfinalObject[] preparedArguments;
/*** 只要有一个ValueHolder对象的converted属性为true,resolveNecessary只就会设置为true*/publicbooleanresolveNecessary=false;
}

复制代码


网络异常,图片无法展示
|

得到 ArgumentsHolder 后,会将其存放到 RootBeanDefinition 对象的属性中;

网络异常,图片无法展示
|

4. 应用入参构建对象

上面介绍了有 BeanDefinition 结构中入参,但还有另外的一个场景,那就是由应用程序传过来的入参,这里简称【应用入参】;针对该场景,我们就不需要采取缓存机制了;所以在上面的流程基础上添加【应用入参】的逻辑,如图所示:

网络异常,图片无法展示
|

5. 创建代理对象

当需要指定方法需要做增强操作时,就需要以代理对象创建的形式;

publicabstractclassAbstractBeanDefinition {
/*** 需要覆盖当前类中的方法*/privateMethodOverridesmethodOverrides;
}

复制代码

这块逻辑的实现,可以查阅《spring 的 IOC 使用以及原理》中有关 Lookup 注解的使用;

2.2 作用域

既然创建出对象了,那么就需要考虑这个对象的所影响区域,也可以理解为对创建对象这个动作进行影响;所以,需要增加一个属性来记录作用域信息;

publicabstractclassAbstractBeanDefinition {
/*** 作用域,这里有默认实现的几种区域;* 1. "" 或 singleton =》 单例* 2. prototype =》 原型* 3. 其他作用域*/privateStringscope=SCOPE_DEFAULT;
}

复制代码

这里补充一下作用域的概念,网上也有,这里就简单的讲一下;

  • 单例: 只有第一次创建对象后,后续就不再创建新的对象,直接复用现有的对象;意味着,在 spring 容器中会有一块内存区域来存放构建好的实例对象;可以简单来说,有缓存机制;
  • 原型:每次创建对象,都是完整的走一遍对象创建流程,并不会缓存起来;
  • 其他:spring 提供了其他类型的作用域,但前提是往 spring 容器中注入 Scope 的实现类才行;

2.3 前期依赖对象

在创建对象过程中,对象中的属性是依赖对象,我这里将其定义为【后期依赖对象】,即创建对象后,再去创建属性对应的对象;然而有特殊的场景,就是创建当前对象前,先创建其他对象,我这里将其定义为【前期依赖对象】;之所以要有【前期依赖对象】,我自己理解为:提前发现问题,减少不必要的初始化动作;例如要创建对象 A,依赖对象的有 B、C 等对象,当创建 C 对象时会发生错误异常;如果先创建了 A 对象,再去创建 C 对象,那么创建 A 对象这个动作是没有必要做的;所以,这个【前期依赖对象】起到了校验效果;

publicabstractclassAbstractBeanDefinition {
/*** 里面的元素是对象名称*/privateString[] dependsOn;
}

复制代码

三. 对象属性注入

当对象创建后,接下来就会对该对象中的属性进行填充;

3.1 自动注入模式

publicabstractclassAbstractBeanDefinition {
/*** 自动注入模式* 0(AUTOWIRE_NO) => 手动注入模式* 1(AUTOWIRE_BY_NAME)=》根据属性名自动注入模式* 2(AUTOWIRE_BY_TYPE)=》根据属性类型自动注入模式* 3(AUTOWIRE_CONSTRUCTOR)=》根据构造方式自动注入模式* 4(AUTOWIRE_AUTODETECT)=》自动检测模式,根据目标类的构造函数中是否有入参;*    如果没有参数,则使用 AUTOWIRE_BY_TYPE ;否则使用 AUTOWIRE_CONSTRUCTOR */privateintautowireMode=AUTOWIRE_NO;
}

复制代码

在属性注入环节中使用该模式的常见,流程如下:

网络异常,图片无法展示
|

上面的流程图中,只涉及三种模式,其余的两种模式,会在哪些场景下使用呢?

  • AUTOWIRE_CONSTRUCTOR 在构建对象时使用。让其构建对象时更加倾向通构造方法去创建对象而已;
  • AUTOWIRE_NO 并无在任何场景下使用,也就是说框架对其不做任何处理;

3.2 有属性对象

publicabstractclassAbstractBeanDefinition {
/*** 对对象的属性进行注入所存放的对象*/privateMutablePropertyValuespropertyValues;
}
publicclassMutablePropertyValues{
/*** 进行*/privatefinalList<PropertyValue>propertyValueList;
/*** 已经处理过的属性名*/privateSet<String>processedProperties;
/*** 是否已经进行转换过*/privatevolatilebooleanconverted=false;
}

复制代码

其流程如下:

网络异常,图片无法展示
|

3.3 Hook 属性注入

在 spring 容器有提供 hook,也就是我们可以通过插件的方式实现属性注入;流程如下:

网络异常,图片无法展示
|

1. 外部属性管理

从上面的场景来看,不难梳理属性注入的优先级:Hook > PropertiesValues > AutoMode;

一旦有优先使用 PropertiesValues 的方式的场景,我们直接在属性声明设置绕过 Hook 的相关代码,例如 Autowired 注解,我们可以不使用该注解即可;

是否有不去掉 @Autowired 注解,也可以优先 PropertiesValues 属性注入呢?是有的,我们需要一个属性来记录哪些属性不需要通过 Hook 来进行属性注入。

publicclassRootBeanDefinition {
/*** 创建对象后,调用前置Hook进行处理,为了避免并发问题,需要加锁*/finalObjectpostProcessingLock=newObject();
/*** true表明已经调用前置Hook进行处理过了,无需再次调用;*/booleanpostProcessed=false;
/*** 由外部程序所管理的属性列表;这里的外部程序主要指的Hook插件*/privateSet<Member>externallyManagedConfigMembers;
}

复制代码

只要我们提前将相关的属性保存到【externallyManagedConfigMembers】对象中去,那么意味其他 Hook 插件程序无权对其属性进行操作;只有对应的 Hook 有权使用;

该【externallyManagedConfigMembers】对象的元素注入,是在创建对象后,属性注入前调用处理的;是调用下面的实现类来处理;

publicinterfaceMergedBeanDefinitionPostProcessorvoidpostProcessMergedBeanDefinition(RootBeanDefinitionbeanDefinition, 
Class<?>beanType, StringbeanName);
}

复制代码

3.4 属性依赖检查策略

当对象的属性注入结束后,需要检测是否有遗漏的属性未注入的;这里有四种策略,来决定属性依赖检测的方式;

public abstract class AbstractBeanDefinition {  /**   * 属性依赖检查策略   * 0(DEPENDENCY_CHECK_NONE)不需要检查   * 1(DEPENDENCY_CHECK_OBJECTS)只检查应用类型   * 2(DEPENDENCY_CHECK_SIMPLE)只检查基础类型   * 3(DEPENDENCY_CHECK_ALL)检查所有   */  private int dependencyCheck = DEPENDENCY_CHECK_NONE;}

复制代码

一般来说,需要一块内存区域记录哪些属性已经初始化的,然后遍历目标类的属性列表,来检查是否有属性未初始化的;然而在 spring 中,并没有这个内存区域来记录这些信息;这块的逻辑,个人理解是有问题的;所幸的是,spring 并没有提供类似注解的形式去修改该属性,所以一般都不会触发属性依赖检测;

四. 对象初始化以及销毁

当对象的属性注入后,将会触发初始化动作;一般来说,实例化动作指的是创建对象;而初始化动作指的调用对象的指定的方法;这里只讲应用程序执行指定的方法,不包含框架自身所提供的方法;

当对象被销毁时,对应的调用对象的销毁方法;

4.1 init&destroy 方法

publicabstractclassAbstractBeanDefinition {
/*** 是否强制调用init方法;这一块只是呈现了校验动作;* 也就是说当其值为true时,init方法必须存在,否则报错*/privatebooleanenforceInitMethod=true;
/*** 与enforceInitMethod的处理逻辑相似*/privatebooleanenforceDestroyMethod=true;
/*** 初始化调用的方法*/privateStringinitMethodName;
/*** 销毁对象所调用的方法*/privateStringdestroyMethodName;
}

复制代码

4.2 Hook 的 init&destroy 方法

这里的 init 方法,是由 Hook 插件自行去调用的;

publicclassRootBeanDefinition {
/*** 由外部程序所管理的init方法列表;这里的外部程序主要指的Hook插件*/privateSet<String>externallyManagedInitMethods;
/*** 由外部程序所管理的销毁方法列表;这里的外部程序主要指的Hook插件*/privateSet<String>externallyManagedDestroyMethods;
}

复制代码

这个逻辑,跟 3.3.1 节一样,这里不再阐述;


五. 对象检索

有关具体的属性对象检索逻辑,可以查阅《spring 篇之属性注入》,这里只阐述相关的属性值描述

从这篇文章中得到过滤目标对象的大体过程是:判断是否是候选人名单 > 判断泛型类型是否匹配的 > 判断限定是否匹配的;

1. 候选人名单

publicabstractclassAbstractBeanDefinition {
/*** 是否将该实例对象作为候选者;*/privatebooleanautowireCandidate=true;
}

复制代码

2. 泛型匹配

publicclassRootBeanDefinition {
/*** 当做解析后的目标对象的泛型类型*/volatileResolvableTypetargetType;
/*** 缓存解析后的目标对象的class类型*/volatileClass<?>resolvedTargetType;
volatileMethodfactoryMethodToIntrospect;
}

复制代码

有关泛型校验逻辑较为复杂,会以另一篇文章进行介绍,这里不再阐述;

3. 限定匹配

publicabstractclassAbstractBeanDefinition {  
/**   * 限定注解对象集合   */privatefinalMap<String, AutowireCandidateQualifier>qualifiers;
}
publicclassRootBeanDefinition{  
/**   * 限定注解   */privateAnnotatedElementqualifiedElement;
}

复制代码

上面这两个属性,在常规的应用程序是很少直接使用的;而是直接在类、属性、方法等声明处,表明限定注解;由于没有看过 spring-test 框架源代码,所以只能猜想该 TEST 框架有可能会使用其操作;

4. 主候选人名单

当经过上面层层的过滤,仍然有多个依赖对象时,需要一个策略,来选择最合适的对象;其中优先级最高的就是这个 primary 属性;

publicabstractclassAbstractBeanDefinition {  
/**   * 是否将该实例对象作为主要的候选者;   * 我们应该谨慎使用该属性,一旦出现两个实例对象都是primary的话,程序就会抛出异常;   */privatebooleanprimary=false;
}

复制代码

六. 其他

6.1 懒加载

publicabstractclassAbstractBeanDefinition {  
/**   * 如果为true时,在spring容器启动,就会去创建对象  * 如果为false时,在使用时,才会触发对象创建   */privatebooleanlazyInit=false;
}

复制代码

6.2 特殊场景

有关 BeanDefinition 结构中大部分的定义都介绍了,只剩下小部分,都是一些特殊特殊场景使用;有后续有使用该场景时,再进行解读;

七. 总结

一个 IOC 框架为围绕对象的构建、属性注入、对象初始化、对象销毁整个环节进行的。上面只是罗列了关键的逻辑,至于一些特殊场景,并没有考虑在内;例如 Hook 机制,其会影响对象创建过程,甚至会改变;

如果结合 Hook 逻辑,那么其就会变得及其复杂,很难解读;

可以查阅我早期发表的文章,在看其文章时,最好是集合源代码阅读;

相关文章
|
6月前
|
XML Java 数据格式
我滴妈!人事竟然问我Spring BeanDefinition是如何帮我们解析和加载的?
我滴妈!人事竟然问我Spring BeanDefinition是如何帮我们解析和加载的?
36 0
我滴妈!人事竟然问我Spring BeanDefinition是如何帮我们解析和加载的?
|
3月前
|
安全 Java Maven
Spring Boot常见企业开发场景应用、自动配置原理结构分析(三)
Spring Boot常见企业开发场景应用、自动配置原理结构分析
|
3月前
|
Java 数据库连接 Spring
Spring Boot常见企业开发场景应用、自动配置原理结构分析(二)
Spring Boot常见企业开发场景应用、自动配置原理结构分析
|
3月前
|
前端开发 Java 数据库连接
Spring Boot常见企业开发场景应用、自动配置原理结构分析(一)
Spring Boot常见企业开发场景应用、自动配置原理结构分析
|
8月前
|
XML 存储 设计模式
Spring高手之路11——BeanDefinition解密:构建和管理Spring Beans的基石
本文对BeanDefinition进行全面深入的探讨,涵盖BeanDefinition的接口方法、主要信息、类型以及生成过程等方面内容。旨在帮助读者全面理解BeanDefinition的各方面知识,并能够熟练应用。文章通俗易懂,具有很强的指导意义。
65 0
Spring高手之路11——BeanDefinition解密:构建和管理Spring Beans的基石
|
4月前
|
存储 Java 测试技术
Spring 拾枝杂谈—Spring原生容器结构剖析(通俗易懂)
Spring 第一节 拾枝杂谈 分析Spring底层容器。
54 0
|
4月前
|
XML Java 程序员
spring 容器结构/机制debug分析和几个重要概念以及IOC 的开发模式
spring 容器结构/机制debug分析和几个重要概念以及IOC 的开发模式
45 0
|
5月前
|
XML Java 数据格式
Spring高手之路16——解析XML配置映射为BeanDefinition的源码
本文提供了深入Spring源码的透彻解析,从XML配置文件的加载开始,步入了Spring的内部世界。通过细致剖析setConfigLocations、refresh和loadBeanDefinitions等方法的实现,揭示了Bean从定义到注册的整个生命周期。
60 1
Spring高手之路16——解析XML配置映射为BeanDefinition的源码
|
8月前
|
XML 存储 Java
Spring高手之路12——BeanDefinitionRegistry与BeanDefinition合并解析
本文深入探讨Spring的BeanDefinition和BeanDefinitionRegistry,详细介绍了BeanDefinition的合并过程及其源码分析,揭示了Spring配置元数据的内在逻辑。读者将通过本文理解Spring Bean定义的继承和重用机制,掌握如何动态注册BeanDefinition。
116 0
Spring高手之路12——BeanDefinitionRegistry与BeanDefinition合并解析
|
10月前
|
XML Java 数据格式
深入理解Spring IOC(二) 、从xml到BeanDefinition(下)
深入理解Spring IOC(二) 、从xml到BeanDefinition(下)
53 0