BeanDefinitions 总结

622 阅读4分钟

这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战

> 1.BeanDefinitions是什么

d5fef8cf-5c74-4004-8d76-dc848884f9ca.png

打开 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 注册。
  1. Resource 定位:Resource 资源的定位需要 Resource 和 ResourceLoader 两个接口互相配合
  2. BeanDefinition 的解析: 是由BeanDefinitionReader 完成的,将用户定义的bean解析成ioc 内部结构:BeanDefinition
  3. 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 不好吗?

像我们平时编写 Classnew 出对象一样,SpringFramework 面对一个应用程序,它也需要对其中的 bean 进行定义抽取,只有抽取成可以统一类型 / 格式的模型,才能在后续的 bean 对象管理时,进行统一管理,也或者是对特定的 bean 进行特殊化的处理。而这一切的一切,最终落地到统一类型上,就是 BeanDefinition 这个抽象化的模型。 简单理解是为了更方便的统一管理。

另外操作BeanDefinition的唯一定义接口是BeanDefinitionRegistry,里面提供了添加,删除,获取的抽象方法,Registry 有注册表的意思,其主要实现是DefaultListableBeanFactory。。这个可以继续读下去,这里不做过多解读了

38419d23d29c83a4758f73f85281e076.png