这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战
> 1.BeanDefinitions是什么
打开 BeanDefinition 的接口定义,从方法列表上看,BeanDefinition 整体包含以下几个部分:
- Bean 的类信息 - 全限定类名 ( beanClassName )
- Bean 的属性 - 作用域 ( scope ) 、是否默认 Bean ( primary ) 、描述信息 ( description ) 等
- Bean 的行为特征 - 是否延迟加载 ( lazy ) 、是否自动注入 ( autowireCandidate ) 、初始化 / 销毁方法 ( initMethod / destroyMethod ) 等
- Bean 与其他 Bean 的关系 - 父 Bean 名 ( parentName ) 、依赖的 Bean ( dependsOn ) 等
- Bean 的配置属性 - 构造器参数 ( constructorArgumentValues ) 、属性变量值 ( propertyValues ) 等
> 2.它是做什么的
前后联系: 它是IOC 过程中的一个重要的结构
- IoC 容器的初始化过程分为三步骤:Resource 定位、BeanDefinition 的载入和解析,BeanDefinition 注册。
- Resource 定位:Resource 资源的定位需要 Resource 和 ResourceLoader 两个接口互相配合
- BeanDefinition 的解析: 是由BeanDefinitionReader 完成的,将用户定义的bean解析成ioc 内部结构:BeanDefinition
- BeanDefinition 注册 :BeanDefinitionRegistry 接口来实现,将BeanDefinition 注入到一个 HashMap 容器中,IoC 容器就是通过这个 HashMap 来维护这些 BeanDefinition 的
注:
1.此时并没有完成没有完成依赖注入(Bean 创建),Bean 创建是发生在应用第一次调用#getBean(...) 方法,向容器索要 Bean
2.对某个 Bean 设置 lazyinit = false 属性,那么这个 Bean 的依赖注入就会在容器初始化的时候完成。
BeanDefinition描述了 SpringFramework 中 bean 的元信息,它包含 bean 的类信息、属性、行为、依赖关系、配置信息等。BeanDefinition具有层次性,并且可以在 IOC 容器初始化阶段被BeanDefinitionRegistryPostProcessor构造和注册,被BeanFactoryPostProcessor拦截修改等。
>3.它是怎么做到的
测试打印 BeanDefinition 信息,前提是有配置好实体类,xml,可以看到打印的类信息
public class BeanDefinitionQuickstartXmlApplication {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("definition/definition-beans.xml");
BeanDefinition personBeanDefinition = ctx.getBeanFactory().getBeanDefinition("person");
System.out.println(personBeanDefinition);
}
}
> 1. Resource 资源的定位
需要 Resource 和 ResourceLoader 两个接口互相配合使用,
XmlBeanDefinitionReader
的#loadBeanDefinitions(EncodedResource encodedResource) 方法。将 Resource 封装成 EncodedResource 主要是为了对 Resource 进行编码
// XmlBeanDefinitionReader.java
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 获取 XML Document 实例
Document doc = doLoadDocument(inputSource, resource);
// 根据 Document 实例,注册 Bean 信息
int count = registerBeanDefinitions(doc, resource);
return count;
}
// ... 省略一堆配置
}
> 2. 转换为 Document 对象
调用 #doLoadDocument(InputSource inputSource, Resource resource) 方法,会将 Bean 定义的资源转换为 Document 对象。代码如下
// XmlBeanDefinitionReader.java
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
类 DefaultDocumentLoader 中提供了实现
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
// 创建 DocumentBuilderFactory
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
// 创建 DocumentBuilder
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
// 解析 XML InputSource 返回 Document 对象
return builder.parse(inputSource);
}
> 3. 注册 BeanDefinition
将 Document其解析为 SpringIoC 管理的 BeanDefinition 对象,并将其注册到容器中,这里使用了
XmlBeanDefinitionReader 的 #registerBeanDefinitions(Document doc, Resource resource) 方法,具体实现了
DefaultBeanDefinitionDocumentReader
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
// 获得 XML Document Root Element
// 执行注册 BeanDefinition
doRegisterBeanDefinitions(doc.getDocumentElement());
}
后面就是从Document 对象中获取根元素 root,然后调用 doRegisterBeanDefinitions(Element root) 方法,开启真正的解析过程。其中包含对节点的判断,使用默认的命名空间(import,alias,bean,beans)还是自定义的解析,使用不同的方法
这里不详解,后面通过一张图片展示
// DefaultBeanDefinitionDocumentReader.java
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 如果根节点使用默认命名空间,执行默认解析
if (delegate.isDefaultNamespace(root)) {
// 遍历子节点
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
// 如果该节点使用默认命名空间,执行默认解析
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
// 如果该节点非默认命名空间,执行自定义解析
} else {
delegate.parseCustomElement(ele);
}
}
}
// 如果根节点非默认命名空间,执行自定义解析
} else {
delegate.parseCustomElement(root);
}
}
注册到容器里去
调用 BeanDefinitionReaderUtils.registerBeanDefinition() 方法,来注册。其实,这里面也是调用 BeanDefinitionRegistry 的 #registerBeanDefinition(String beanName, BeanDefinition beanDefinition) 方法,来注册 BeanDefinition 。不过,最终的实现是在 DefaultListableBeanFactory 中实现
//DefaultListableBeanFactory.java
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
// ...省略校验相关的代码
// 从缓存中获取指定 beanName 的 BeanDefinition
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
// 如果已经存在
if (existingDefinition != null) {
// 如果存在但是不允许覆盖,抛出异常
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
} else {
// ...省略 logger 打印日志相关的代码
}
// 【重点】允许覆盖,直接覆盖原有的 BeanDefinition 到 beanDefinitionMap 中。
this.beanDefinitionMap.put(beanName, beanDefinition);
// 如果未存在
} else {
// ... 省略非核心的代码
// 【重点】添加到 BeanDefinition 到 beanDefinitionMap 中。
this.beanDefinitionMap.put(beanName, beanDefinition);
}
// 重新设置 beanName 对应的缓存
if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}
关键语句: this.beanDefinitionMap.put(beanName, beanDefinition); 实际就是一个map 。key为beanName ,value为BeanDefinition对象。
> 4. 总结
IoC 的初始化过程就已经完成了,从 Bean 资源的定位,转换为 Document 对象,接着对其进行解析,最后注册到 IoC 容器中,都已经完美地完成了。现在 IoC 容器中已经建立了整个 Bean 的配置信息,这些 Bean 可以被检索、使用、维护,他们是控制反转的基础,是后面注入 Bean 的依赖。最后用一张流程图来结束这篇总结之文。
> 5. 补充
最后会问出一个问题 SpringFramework 为什么会设计 BeanDefinition 呢?直接注册 Bean 不好吗?
像我们平时编写 Class 再 new 出对象一样,SpringFramework 面对一个应用程序,它也需要对其中的 bean 进行定义抽取,只有抽取成可以统一类型 / 格式的模型,才能在后续的 bean 对象管理时,进行统一管理,也或者是对特定的 bean 进行特殊化的处理。而这一切的一切,最终落地到统一类型上,就是 BeanDefinition 这个抽象化的模型。 简单理解是为了更方便的统一管理。
另外操作BeanDefinition的唯一定义接口是BeanDefinitionRegistry,里面提供了添加,删除,获取的抽象方法,Registry 有注册表的意思,其主要实现是DefaultListableBeanFactory。。这个可以继续读下去,这里不做过多解读了