一、BeanDefinition

1.1 什么是 BeanDefinition

在一般的 Spring 项目中,主要通过 XML 的方式配置 bean,而 BeanDefinition 就是 XML 配置属性的载体,XML 文件首先会被转化成 Document 对象,通过解析 Document,把 XML 中 <bean /> 标签转化成 BeanDefinition 供 IoC 容器创建 bean 时使用。

我们可以来做个测试。

    <bean id="typeMismatch" class="org.springframework.tests.sample.beans.TestBean" scope="prototype"><property name="name"><value>typeMismatch</value></property><property name="age"><value>34x</value></property><property name="spouse"><ref bean="rod"/></property></bean>

下面是 debug 后抓到的 BeanDefinition 属性。

1.2 BeanDefinition 初始化流程图

二、源码分析

Debug 测试类入口:org.springframework.beans.factory.xml.XmlBeanDefinitionReaderTests#withOpenInputStream

下面只是把核心流程拿出来作了分析,一些细节知识点,有兴趣的可以自行了解。

测试代码如下

    @Test(expected = BeanDefinitionStoreException.class)public void withOpenInputStream() {/*** 注意这里初始化的是 SimpleBeanDefinitionRegistry 不具备 BeanFactory 功能* 仅仅用来注册 BeanDefinition,不能用来创建 bean*/SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();Resource resource = new InputStreamResource(getClass().getResourceAsStream("test.xml"));new XmlBeanDefinitionReader(registry).loadBeanDefinitions(resource);}

loadBeanDefinitions 源码如下

    @Overridepublic int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {// 使用 EncodedResource 包装 Resource,EncodedResource 可以指定字符集编码return loadBeanDefinitions(new EncodedResource(resource));}public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {Assert.notNull(encodedResource, "EncodedResource must not be null");if (logger.isTraceEnabled()) {logger.trace("Loading XML bean definitions from " + encodedResource);}// 获取已经加载过的资源集合Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();if (currentResources == null) {// 初始化 currentResourcescurrentResources = new HashSet<>(4);// 设置初始化的 currentResourcesthis.resourcesCurrentlyBeingLoaded.set(currentResources);}if (!currentResources.add(encodedResource)) {throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");}try {// 根据 Resource 获取输入流InputStream inputStream = encodedResource.getResource().getInputStream();try {InputSource inputSource = new InputSource(inputStream);if (encodedResource.getEncoding() != null) {inputSource.setEncoding(encodedResource.getEncoding());}// 核心逻辑,加载 bean 资源return doLoadBeanDefinitions(inputSource, encodedResource.getResource());}finally {inputStream.close();}}catch (IOException ex) {throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), ex);}finally {currentResources.remove(encodedResource);if (currentResources.isEmpty()) {this.resourcesCurrentlyBeingLoaded.remove();}}}

2.1 获取输入流

测试类中定义的是 InputStreamResource,下面 InputStreamResourcegetInputStream() 的实现。

    @Overridepublic InputStream getInputStream() throws IOException {InputStream is;if (this.clazz != null) {is = this.clazz.getResourceAsStream(this.path);}else if (this.classLoader != null) {is = this.classLoader.getResourceAsStream(this.path);}else {is = ClassLoader.getSystemResourceAsStream(this.path);}if (is == null) {throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");}return is;}

2.2 转化 Document 对象

    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {try {// 获取 XML 对应 document 实例Document doc = doLoadDocument(inputSource, resource);// 调用 registerBeanDefinitions 方法注册 BeanDefinitionsint count = registerBeanDefinitions(doc, resource);if (logger.isDebugEnabled()) {logger.debug("Loaded " + count + " bean definitions from " + resource);}return count;}catch (Throwable ex) {throw new BeanDefinitionStoreException(resource.getDescription(),"Unexpected exception parsing XML document from " + resource, ex);}}

doLoadBeanDefinitions 方法中调用 doLoadDocument 初始化 Document 对象,内部实现比较简单,下面一起来看一下。

    protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,getValidationModeForResource(resource), isNamespaceAware());}

上面涉及到几个关于 XML 的知识点,下面简单的介绍一下,最后有列出参考文章,有兴趣的可以翻翻。

  • EntityResolver:XML 文件解析器
  • errorHandler:解析出错处理机制
  • getValidationModeForResource(): 获取 XML 验证格式,XML 一般支持 DTD 与 XSD,也可以自定义,主要用来约束与验证 XML 文档格式
  • isNamespaceAware():判断解析器是否支持解析当前 XML 文件,<beans xmlns=""/> 其中 xmlns 就是命名空间
    @Overridepublic Document loadDocument(InputSource inputSource, EntityResolver entityResolver,ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {// 创建 DocumentBuilderFactoryDocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);if (logger.isTraceEnabled()) {logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");}// 创建一个 DocumentBuilderDocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);// 以 SAX 形式解析 XMLreturn builder.parse(inputSource);}

上面 DocumentBuilderFactoryDocumentBuilder 都是 JDK 中提供的类,根据 XML 输入流获取 Document 的过程没有深入跟踪,这里就不展开分析了。

2.3 处理 Document 节点

    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {// 通过反射创建一个 BeanDefinitionDocumentReader 对象BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();// 获取已经注册的 BeanDefinition 的数量,-> beanDefinitionMap 的 sizeint countBefore = getRegistry().getBeanDefinitionCount();// 创建 XmlReaderContext,注册 BeanDefinitionsdocumentReader.registerBeanDefinitions(doc, createReaderContext(resource));// 返回最新注册的 bean 的数量return getRegistry().getBeanDefinitionCount() - countBefore;}@Overridepublic void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {this.readerContext = readerContext;doRegisterBeanDefinitions(doc.getDocumentElement());}protected void doRegisterBeanDefinitions(Element root) {BeanDefinitionParserDelegate parent = this.delegate;this.delegate = createDelegate(getReaderContext(), root, parent);// 检查 <beans> 标签的命名空间是否为空,或者是 http://www.springframework.org/schema/beansif (this.delegate.isDefaultNamespace(root)) {// 获取 profile 的值,beans 标签可以设置 profile 属性用于多环境配置管理String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);if (StringUtils.hasText(profileSpec)) {// 处理 profile 多个值String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);// 判断是否有默认启用的 profileif (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {if (logger.isDebugEnabled()) {logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +"] not matching: " + getReaderContext().getResource());}return;}}}// 解析前处理,空实现,可自定义preProcessXml(root);// 解析 document 实例parseBeanDefinitions(root, this.delegate);// 解析后处理,空实现,可自定义postProcessXml(root);this.delegate = parent;}protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {// 命名空间检查,Spring XML 中不仅可以配置 <beans /> 标签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 {// samples:<tx:annotation-driven>delegate.parseCustomElement(root);}}private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {// import,samples:<import resource="classpath:/org/springframework/beans/factory/xml/test.xml"/>if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {// 根据 resource 值定位资源,递归调用 loadBeanDefinitions 逐个加载importBeanDefinitionResource(ele);}// aliaselse if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {// 处理别名标签,最终注册到 aliasMap 中processAliasRegistration(ele);}// beanelse if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {// 转化成 BeanDefinitionprocessBeanDefinition(ele, delegate);}// beanselse if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {// 递归调用doRegisterBeanDefinitions(ele);}}

在获取 Document 对象后,后续流程会遍历所有子节点,根据子标签名分别走不同的处理流程,我们主要是来了解怎么初始化与注册 BeanDefinition 的,其他标签就不详细介绍了。

  1. 利用反射创建 BeanDefinitionDocumentReader 对象
  2. 获取已经注册的 BeanDefinition 的数量,其实就是 beanDefinitionMap 的 size 大小
  3. 检查 <beans /> 标签命名空间是否为空,或者配置为 http://www.springframework.org/schema/beans,如果条件满足,获取 profile 指定的环境属性值,判断指定的环境是否有处于启用状态的,都不启用直接返回,不会对 Document 进行解析,关于 profile 属性的作用,最后会给一些参考文章
  4. 解析前置处理,空实现,可自定义
  5. 如果是默认命名空间,获取 下所有子标签,进行遍历,判断子标签是 importaliasbean 还是 beans
  6. 解析后置处理,空实现,可自定义

2.3 BeanDefinition 初始化

    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {// 处理 <bean /> 标签,把标签属性与子标签信息封装在 BeanDefinition 中BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);if (bdHolder != null) {// 装饰 BeanDefinitionbdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);try {// Register the final decorated instance.// 注册 BeadDefinitionBeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());}catch (BeanDefinitionStoreException ex) {getReaderContext().error("Failed to register bean definition with name '" +bdHolder.getBeanName() + "'", ele, ex);}// Send registration event.// 发送解析注册完成响应事件,通知相关的监听器getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));}}@Nullablepublic BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {return parseBeanDefinitionElement(ele, null);}@Nullablepublic BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {// <bean name="b1,b2,b3" /> id 与 name 的属性功能类似,name 属性支持创建多个别名// 获取 id 属性String id = ele.getAttribute(ID_ATTRIBUTE);// 获取 name 属性String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);// 处理 name 属性的别名List<String> aliases = new ArrayList<>();if (StringUtils.hasLength(nameAttr)) {String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);aliases.addAll(Arrays.asList(nameArr));}// 优先使用 id 属性的别名String beanName = id;// 如果 id 属性为空,且 name 属性不为空使用 name 属性中的第一个if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {// 移出别名集合beanName = aliases.remove(0);if (logger.isTraceEnabled()) {logger.trace("No XML 'id' specified - using '" + beanName +"' as bean name and " + aliases + " as aliases");}}// 需要检查 beanName 的唯一性,避免与其他 bean 重复if (containingBean == null) {checkNameUniqueness(beanName, aliases, ele);}// 获取 AbstractBeanDefinition 实例,该对象中保存了 <bean /> 标签中的基本属性信息AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);if (beanDefinition != null) {// TODO 定义一个 bean 并不一必须定要为其定义 id、name 属性// 如果没有定义 id 或者 name 属性,则想办法生成 beanNameif (!StringUtils.hasText(beanName)) {try {if (containingBean != null) {/*** 使用 className 属性生成 beanName* beanName + # + beanDefinition 哈希值的十六进制字符*/beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);}else {beanName = this.readerContext.generateBeanName(beanDefinition);String beanClassName = beanDefinition.getBeanClassName();if (beanClassName != null &&beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {aliases.add(beanClassName);}}if (logger.isTraceEnabled()) {logger.trace("Neither XML 'id' nor 'name' specified - " +"using generated bean name [" + beanName + "]");}}catch (Exception ex) {error(ex.getMessage(), ele);return null;}}// 构造 BeanDefinitionHolder 对象并返回String[] aliasesArray = StringUtils.toStringArray(aliases);return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);}return null;}@Nullablepublic AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) {// 将 beanName 存储到一个 LinkedList 中this.parseState.push(new BeanEntry(beanName));// 记录 classNameString className = null;// 获取 class 属性if (ele.hasAttribute(CLASS_ATTRIBUTE)) {className = ele.getAttribute(CLASS_ATTRIBUTE).trim();}String parent = null;// 判断是否有 parent 属性if (ele.hasAttribute(PARENT_ATTRIBUTE)) {parent = ele.getAttribute(PARENT_ATTRIBUTE);}try {// 创建用于承载 XML 属性配置的 AbstractBeanDefinition 实例AbstractBeanDefinition bd = createBeanDefinition(className, parent);// 解析 <bean /> 标签的各种属性,比如 scope、lazy-init、autowire 等parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);// 解析描述信息bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));/*** 以下处理 <bean /> 的子标签,这里可以优化,循环一次对子标签分类处理* 下面每处理一个子标签就需要循环一次 <bean /> 所有的子标签*/// 处理 <meta/>parseMetaElements(ele, bd);// 解析 lookup-method 属性 <lookup-method />parseLookupOverrideSubElements(ele, bd.getMethodOverrides());// 解析 replaced-method 属性 <replaced-method />parseReplacedMethodSubElements(ele, bd.getMethodOverrides());// 解析构造函数参数 <constructor-arg />parseConstructorArgElements(ele, bd);// 解析 property 子元素 <property />parsePropertyElements(ele, bd);// 解析 qualifier 子元素 <qualifier />parseQualifierElements(ele, bd);// 设置 Resource 实例bd.setResource(this.readerContext.getResource());bd.setSource(extractSource(ele));return bd;}catch (Throwable ex) {error("Unexpected failure during bean definition parsing", ele, ex);}finally {this.parseState.pop();}return null;}

初始化 BeanDefinition 的过程就是把 <bean /> 标签及子标签的属性保存到 BeanDefinition 中,没有什么复杂的逻辑。

2.4 注册 BeanDefinition

    public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)throws BeanDefinitionStoreException {// Register bean definition under primary name.// 获取 beanName 并根据 beanName 注册 BeanDefinitionString beanName = definitionHolder.getBeanName();registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());// Register aliases for bean name, if any.// 从 BeanDefinition 中获取所有的别名,并根据 beanName 注册别名String[] aliases = definitionHolder.getAliases();if (aliases != null) {for (String alias : aliases) {// 注册所有的别名,保存到 aliasMap 中registry.registerAlias(beanName, alias);}}}@Overridepublic void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException {Assert.hasText(beanName, "'beanName' must not be empty");Assert.notNull(beanDefinition, "BeanDefinition must not be null");// 保存到 beanDefinitionMap 中this.beanDefinitionMap.put(beanName, beanDefinition);}

因为测试类中定义的是 SimpleBeanDefinitionRegistry,因此应该定位到 SimpleBeanDefinitionRegistry 中的 registerBeanDefinition,这里的处理流程很简单,直接把 BeanDefinitionbeanName 关联保存到 beanDefinitionMap 中。

SimpleBeanDefinitionRegistry 并不是一个工厂,不具备初始化 bean 的能力。后面在创建 bean 的流程中还会接触到 DefaultListableBeanFactory#registerBeanDefinition 注册流程,稍微比这个复杂些,但是其核心逻辑都是保存到 beanDefinitionMap

参考阅读

XML中DTD,XSD的区别与应用
xsd,dtd,tld有什么区别和联系?
细说java解析XML文档的常用方法(含实例)
详解Spring中的Profile

Spring IoC 源码系列(一)BeanDefinition 初始化与注册相关推荐

  1. Spring IoC 源码系列(五)getBean 流程分析

    一.FactoryBean 用法讲解 在分析源码流程之前,我们先来看一下 FactoryBean,乍一看这家伙和 BeanFactory 很像,它们都可以用来获取 bean 对象,简单来说 Facto ...

  2. Spring IoC 源码系列(四)bean创建流程与循环依赖问题分析

    创建单例 bean 的代码细节在 org.springframework.beans.factory.support.AbstractBeanFactory#getBean 中,getBean 顾名思 ...

  3. Spring IoC 源码系列(三)Spring 事件发布机制原理分析

    在 IoC 容器启动流程中有一个 finishRefresh 方法,具体实现如下: protected void finishRefresh() {clearResourceCaches();init ...

  4. Spring IoC 源码导读

    源码记录:spring-framework-5.1.7-source-code-read 文章导读 Spring IoC 源码系列(一)BeanDefinition 初始化与注册 Spring IoC ...

  5. Spring源码系列:BeanDefinition载入(下)

    在Spring源码系列:BeanDefinition载入(上)中已经大概捋了一下解析过程,本篇将记录一下bean的注册过程. bean的注册就是DefaultListableBeanFactory中r ...

  6. Spring IoC源码:getBean 详解

    文章目录 Spring源码系列: 前言 正文 方法1:getObjectForBeanInstance 方法2:getObjectFromFactoryBean 方法3:doGetObjectFrom ...

  7. Spring读源码系列之AOP--03---aop底层基础类学习

    Spring读源码系列之AOP--03---aop底层基础类学习 引子 Spring AOP常用类解释 AopInfrastructureBean---免被AOP代理的标记接口 ProxyConfig ...

  8. Spring源码系列:BeanDefinition源码解析

    Bean的定义主要由BeanDefinition来描述的.作为Spring中用于包装Bean的数据结构,今天就来看看它的面纱下的真容吧. 首先就是BeanDefinition的类定义: public ...

  9. spring源码系列一--BeanDefinition

    如果说java是由对象组成,那么spring-framework框架可以说是由BeanDefinition所构成.BeanDefinitiion其实是spring中的顶级接口,我们在阅读源码之前必须要 ...

最新文章

  1. 机器学习与数据科学 基于R的统计学习方法(基础部分)
  2. 盘点深度学习一年来在文本、语音和视觉等方向的进展,看强化学习如何无往而不利
  3. mysql数据迁移 脚本_PHP将数据从Oracle向Mysql数据迁移实例
  4. 计算机算法知识总结,移动笔试知识点之--计算机类-数据结构与算法知识点总结.pdf...
  5. leetcode - 53. 最大子序和
  6. win10我的电脑在哪里找到
  7. ActiveMQ 使用文档
  8. bzoj 1930: [Shoi2003]pacman 吃豆豆 [费用流]
  9. SQL Server 2008无日志文件附加数据库
  10. 近6年被引用次数最多的深度学习论文top100(附下载地址)
  11. android 仿站小工具,仿站小工具下载
  12. kali2020 中文乱码问题
  13. 2021年中青杯 B题 港珠澳车辆通行(详细解题思路)
  14. 免费企业网站模板_学校网站模板_政府网站模板源码下载
  15. python头像转卡通_用python将你的头像“卡通化”
  16. 移动云平台的基础架构之旅(一):云应用
  17. 单、双目相机标定及其校正相关函数整理
  18. 为micropython添加模块(2)-类模块
  19. SecureCRT显示乱码的解决办法(centos)
  20. 拓扑序列(拓扑排序)

热门文章

  1. No MyBatis mapper was found in ‘[xx.mapper]‘ package. Please check your configuration
  2. 牛客题霸 NC27 集合的所有子集
  3. JavaScript——易班优课YOOC课群在线测试自动答题解决方案(一)答案获取
  4. Nginx——域名|端口|目录请求转发配置DEMO
  5. BugKuCTF WEB web基础$_GET
  6. SUM and REPLACE
  7. linux压缩和打包的区别,Linux中的压缩和打包
  8. JavaScript基础06-day08【if练习、条件分支语句switch、for循环】
  9. 【CentOS Linux 7】【Linux系统及应用---调研报告】
  10. 01-CoreData简介