SpringIOC源码解析(5)—— 通过document对象解析出BeanDefinition实例

本文涉及的产品
云解析DNS-重点域名监控,免费拨测 20万次(价值200元)
简介: locationBeanDefinitionReaderResourceBeanDefinitionRegistryResourceloaderDefaultListableBeanFactory

《SpringIOC源码解析(3)—— Resource、ResourceLoader、容器之间的微妙关系》文末,将xml文件解析成了Document对象,接下来就该调用registerBeanDefinitions来解析Document对象。


public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
   // 创建BeanDefinitionDocumentReader,这个是实际从XML的DOM树中读取BeanDefiniton
   BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
   // 获取注册表beanDefinitionMap的在本次加载前的BeanDefinition数量
   int countBefore = getRegistry().getBeanDefinitionCount();
   // 加载并注册
   documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
   // 本次加载注册后容器里BeanDefinition的数量减去先前的,即本次加载的BeanDefinition数量
   return getRegistry().getBeanDefinitionCount() - countBefore;
}


先看一下

createBeanDefinitionDocumentReader方法:


protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
   return BeanUtils.instantiateClass(this.documentReaderClass);
}

该方法会传入一个documentReaderClass实例对象,传入的的class如下:


private Class<? extends BeanDefinitionDocumentReader> documentReaderClass =
      DefaultBeanDefinitionDocumentReader.class;


DefaultBeanDefinitionDocumentReader是BeanDefinitionDocumentReader的默认实现


0.png


registerBeanDefinitions的倒数第三行和倒数第一行代码就揭示了之前说的loadBeanDefinitions如何获取到本次加载的BeanDefinition的数量:


加载前先统计下注册表中BeanDefinition的数量

加载后再去统计下注册表中BeanDefinition的数量,两者计算差值

统计BeanDefinition的数量的方法:


@Override
public int getBeanDefinitionCount() {
   return this.beanDefinitionMap.size();
}

this就是上面getRegistry()返回的DefaultListableBeanFactory实例,DefaultListableBeanFactory实现了BeanDefinitionRegistry接口。


beanDefinitionMap是BeanDefinition实例最终注册到的地方


接下来

继续分析registerBeanDefinitions:


// 加载并注册
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));

进入到registerBeanDefinitions方法


@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
   this.readerContext = readerContext;
   doRegisterBeanDefinitions(doc.getDocumentElement());
}

readerContext是经过包装的Resource对象,调用了doRegisterBeanDefinitions这个真正干活的do方法。


protected void doRegisterBeanDefinitions(Element root) {
   // Any nested <beans> elements will cause recursion in this method. In
   // order to propagate and preserve <beans> default-* attributes correctly,
   // keep track of the current (parent) delegate, which may be null. Create
   // the new (child) delegate with a reference to the parent for fallback purposes,
   // then ultimately reset this.delegate back to its original (parent) reference.
   // this behavior emulates a stack of delegates without actually necessitating one.
   // BeanDefintion解析委托类
   BeanDefinitionParserDelegate parent = this.delegate;
   this.delegate = createDelegate(getReaderContext(), root, parent);
   // 判断这个根节点是否是默认的命名空间,
   // 底层就是判断这个根节点的nameSpaceUrl=="http://www.springframework.org/schema/beans"
   if (this.delegate.isDefaultNamespace(root)) {
      // 获取这个profile属性的值,表示剖面,用于设置环境
      String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
      if (StringUtils.hasText(profileSpec)) {
         // 根据分隔符换换成数组
         String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
               profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
         // We cannot use Profiles.of(...) since profile expressions are not supported
         // in XML config. See SPR-12458 for details.
         // 判断这个切面是否是激活的环境,如果不是直接返回,表示这个配置文件不是当前运行环境的配置文件
         if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
            if (logger.isDebugEnabled()) {
               logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                     "] not matching: " + getReaderContext().getResource());
            }
            return;
         }
      }
   }
   // 在解析xml之前做的准备工作,其实什么也没做
   preProcessXml(root);
   // 调用这个方法,解析
   parseBeanDefinitions(root, this.delegate);
   // 后续处理的
   postProcessXml(root);
   this.delegate = parent;
}


BeanDefinitionParserDelegate是个代理类,一会儿会委托这个对象来加载Document对象,this.delegate是具体的实例。


this.delegate.isDefaultNamespace(root)判断该xml是否是按照Spring定义的规范来编写的,如果是Spring定义的规则的话,就会尝试去获取profile标签:

String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);


profile主要用来区分项目的环境,可以用来区分线上或线下的环境(foodie的dev和product)

通过了上面的验证之后,doRegisterBeanDefinitions最后有两个钩子方法,方便去做定制化的事前和事后的处理,默认是没有任何实现。


preProcessXml(root);
postProcessXml(root);


两个钩子方法中间的是:


parseBeanDefinitions(root, this.delegate);


接收传进来的Document对象和传进来的BeanDefinitionParserDelegate对象


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;
            // Bean定义的Document对象使用了Spring默认的XML命名空间
            if (delegate.isDefaultNamespace(ele)) {
               // 若是则按照spring原有的逻辑进行解析
               parseDefaultElement(ele, delegate);
            }
            else {
               // 否则使用扩展的自定义代理类进行解析
               delegate.parseCustomElement(ele);
            }
         }
      }
   }
   else {
      // 使用扩展的自定义代理类进行解析
      delegate.parseCustomElement(root);
   }
}


 上面的代码会先判断Document是否符合Spring的规定,否则就有可能是用户自定义的另外一套的识别方案了。一般情况下是会进入到if里的,先从根部开始获取所有的子节点,并配合代理类对象delegate去解析,最终会调用parseDefaultElement。


 在方法里会根据不同的标签去处理不同的逻辑

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
   // 如果元素节点是<Import>导入元素,进行导入解析
   // <import resource="classpath:applicationContext-datasource.xml"/>
   if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
      importBeanDefinitionResource(ele);
   }
   // 如果元素节点是<Alias>别名元素,进行别名解析
   else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
      processAliasRegistration(ele);
   }
   // 元素节点既不是导入元素,也不是别名元素,即普通的<Bean>元素,
   // 按照Spring的Bean规则解析元素
   else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
      processBeanDefinition(ele, delegate);
   }
   // 元素节点既不是导入元素,也不是别名元素,即普通的<Bean>元素,
   // 按照Spring的Bean规则解析元素
   else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
      // recurse
      doRegisterBeanDefinitions(ele);
   }
}


注册完毕后会发送一个事件进行通知。这里我们主要关注与bean标签相关的处理逻辑。processBeanDefinition(ele, delegate);


protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
   // BeanDefinitionHolder是对BeanDefinition的封装,即Bean定义的封装类
   // 对Document对象中<Bean>元素的解析由BeanDefinitionParserDelegate实现
   BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
   if (bdHolder != null) {
      bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
      try {
         // Register the final decorated instance.
         // 向Spring IOC容器注册解析得到的BeanDefinition,这是BeanDefinition向IOC容器注册的入口
         BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
      }
      catch (BeanDefinitionStoreException ex) {
         getReaderContext().error("Failed to register bean definition with name '" +
               bdHolder.getBeanName() + "'", ele, ex);
      }
      // Send registration event.
      // 在完成BeanDefinition注册之后,往容器发送注册完成的事件
      getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
   }
}

 这里终于开始调用delegate.parseBeanDefinitionElement(ele)来解析dom元素了,之所以层层调用,主要是考虑到了程序扩展性,中间有各种钩子和子类方法。


 使用BeanDefinitionHolder来承接解析出来的BeanDefinition对象,本质上就是一个包装类


1.png

回到上面第4行调用的方法:


@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
   return parseBeanDefinitionElement(ele, null);
}


这里第二个参数传null表示bean标签没有嵌套的子bean标签。


public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
   // 获取<Bean>元素中的id属性值
   String id = ele.getAttribute(ID_ATTRIBUTE);
   // 获取<Bean>元素中的name属性值
   // <bean class="study.spring.bean.Bean" id="bean1" name="firstBean 1stBean"></bean>
   String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
   List<String> aliases = new ArrayList<>();
   // 将<Bean>元素中的所有name属性值存放到别名中
   if (StringUtils.hasLength(nameAttr)) {
      String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
      aliases.addAll(Arrays.asList(nameArr));
   }
   String beanName = id;
   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");
      }
   }
   // 元素中是否包含嵌套<Bean>元素
   if (containingBean == null) {
      // 检查<Bean>元素所配置的id、name或者别名是否重复
      checkNameUniqueness(beanName, aliases, ele);
   }
   // 对 <bean> 标签的其他属性进行解析
   AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
   // 生要给BeanDefinition起名字
   if (beanDefinition != null) {
      if (!StringUtils.hasText(beanName)) {
         try {
            if (containingBean != null) {
               beanName = BeanDefinitionReaderUtils.generateBeanName(
                     beanDefinition, this.readerContext.getRegistry(), true);
            }
            else {
               beanName = this.readerContext.generateBeanName(beanDefinition);
               // Register an alias for the plain bean class name, if still possible,
               // if the generator returned the class name plus a suffix.
               // This is expected for Spring 1.2/2.0 backwards compatibility.
               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;
         }
      }
      String[] aliasesArray = StringUtils.toStringArray(aliases);
      return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
   }
   return null;
}


该方法有很大一段逻辑是关于起名的,这里主要关注起名之后的parseBeanDefinitionElement方法

该方法返回一个AbstractBeanDefinition类型的实例,这就是我们一直要寻找的BeanDefinition实例了

@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(
      Element ele, String beanName, @Nullable BeanDefinition containingBean) {
   this.parseState.push(new BeanEntry(beanName));
   String className = null;
   // 如果<Bean>元素中配置了class属性,则获取class属性的值
   if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
      className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
   }
   String parent = null;
   // 如果<Bean>元素中配置了parent属性,则获取parent属性的值
   if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
      parent = ele.getAttribute(PARENT_ATTRIBUTE);
   }
   try {
      // 根据<Bean>元素配置的class名称和parent属性值创建BeanDefinition
      // 为载入Bean定义信息做准备
      AbstractBeanDefinition bd = createBeanDefinition(className, parent);
      /**解析<bean>标签里的各种属性值,将其set进bd里面*/
      parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
      bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
      parseMetaElements(ele, bd);
      parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
      parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
      parseConstructorArgElements(ele, bd);
      parsePropertyElements(ele, bd);
      parseQualifierElements(ele, bd);
      bd.setResource(this.readerContext.getResource());
      bd.setSource(extractSource(ele));
      return bd;
   }
   catch (ClassNotFoundException ex) {
      error("Bean class [" + className + "] not found", ele, ex);
   }
   catch (NoClassDefFoundError err) {
      error("Class that bean class [" + className + "] depends on not found", ele, err);
   }
   catch (Throwable ex) {
      error("Unexpected failure during bean definition parsing", ele, ex);
   }
   finally {
      this.parseState.pop();
   }
   return null;
}


方法里主要是解析bean标签的其他属性值。

进入到createBeanDefinition中,发现这里面创建出了BeanDefinition实例


protected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName)
      throws ClassNotFoundException {
   return BeanDefinitionReaderUtils.createBeanDefinition(
         parentName, className, this.readerContext.getBeanClassLoader());
}

进入到Util的createBeanDefinition中发现最终返回的是GenericBeanDefinition实例,从这里可以看到GenericBeanDefinition已经成为了Spring中主要的BeanDefinition


public static AbstractBeanDefinition createBeanDefinition(
      @Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {
   GenericBeanDefinition bd = new GenericBeanDefinition();
   bd.setParentName(parentName);
   if (className != null) {
      if (classLoader != null) {
         bd.setBeanClass(ClassUtils.forName(className, classLoader));
      }
      else {
         bd.setBeanClassName(className);
      }
   }
   return bd;
}

 回到parseBeanDefinitionElement方法,剩下的内容主要就是对bean剩下的内容解析,解析完成之后会将相应的属性值set到BeanDefinition里。最后返回GenericBeanDefinition实例。


执行完毕后又回到DefaultBeanDefinitionDocumentReader的processBeanDefinition方法里

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
   // BeanDefinitionHolder是对BeanDefinition的封装,即Bean定义的封装类
   // 对Document对象中<Bean>元素的解析由BeanDefinitionParserDelegate实现
   BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
   if (bdHolder != null) {
      bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
      try {
         // Register the final decorated instance.
         // 向Spring IOC容器注册解析得到的BeanDefinition,这是BeanDefinition向IOC容器注册的入口
         BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
      }
      catch (BeanDefinitionStoreException ex) {
         getReaderContext().error("Failed to register bean definition with name '" +
               bdHolder.getBeanName() + "'", ele, ex);
      }
      // Send registration event.
      // 在完成BeanDefinition注册之后,往容器发送注册完成的事件
      getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
   }
}


此时已经执行完了


BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);

之后还会看一下是否需要根据用户自定义的逻辑来处理额外的一些属性

bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);

这一步属于定制化扩展,关键的是


BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());


调用该方法会向容器里注册BeanDefinition,也就是往map中写入,注册完成后往容器发送注册完成的事件。


执行完毕之后又回到parseDefaultElement方法,当遇到<beans>标签后证明会有很多<bean>标签,然后执行doRegisterBeanDefinitions(ele);方法完成和上面相同的步骤。


private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
   // 如果元素节点是<Import>导入元素,进行导入解析
   // <import resource="classpath:applicationContext-datasource.xml"/>
   if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
      importBeanDefinitionResource(ele);
   }
   // 如果元素节点是<Alias>别名元素,进行别名解析
   else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
      processAliasRegistration(ele);
   }
   // 元素节点既不是导入元素,也不是别名元素,即普通的<Bean>元素,
   // 按照Spring的Bean规则解析元素
   else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
      processBeanDefinition(ele, delegate);
   }
   // 元素节点既不是导入元素,也不是别名元素,即普通的<Bean>元素,
   // 按照Spring的Bean规则解析元素
   else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
      // recurse
      doRegisterBeanDefinitions(ele);
   }
}
相关文章
|
9月前
|
存储 缓存 负载均衡
阿里云服务器实例选择指南:热门实例性能、适用场景解析对比参考
2025年,在阿里云的活动中,主售的云服务器实例规格除了轻量应用服务器之外,还有经济型e、通用算力型u1、计算型c8i、通用型g8i、计算型c7、计算型c8y、通用型g7、通用型g8y、内存型r7、内存型r8y等,以满足不同用户的需求。然而,面对众多实例规格,用户往往感到困惑,不知道如何选择。本文旨在全面解析阿里云服务器实例的各种类型,包括经济型、通用算力型、计算型、通用型和内存型等,以供参考和选择。
|
9月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
876 29
|
9月前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
357 4
|
9月前
|
存储 机器学习/深度学习 人工智能
阿里云服务器第八代通用型g8i实例评测:性能与适用场景解析
阿里云服务器通用型g8i实例怎么样?g8i实例采用CIPU+飞天技术架构,并搭载最新的Intel 第五代至强可扩展处理器(代号EMR),不仅性能得到大幅提升,同时还拥有AMX加持的AI能力增强,以及全球范围内率先支持的TDX机密虚拟机能力。这些特性使得g8i实例在AI增强和全面安全防护两大方面表现出色,尤其适用于在线音视频及AI相关应用。本文将深入探讨g8i实例的产品特性、优势、适用场景及规格族,以帮助您更好地了解这款产品,以供参考和选择。
|
9月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
9月前
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
360 2
|
9月前
|
移动开发 前端开发 JavaScript
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。
|
9月前
|
存储 前端开发 JavaScript
在线教育网课系统源码开发指南:功能设计与技术实现深度解析
在线教育网课系统是近年来发展迅猛的教育形式的核心载体,具备用户管理、课程管理、教学互动、学习评估等功能。本文从功能和技术两方面解析其源码开发,涵盖前端(HTML5、CSS3、JavaScript等)、后端(Java、Python等)、流媒体及云计算技术,并强调安全性、稳定性和用户体验的重要性。
|
10月前
|
机器学习/深度学习 自然语言处理 算法
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
2550 1

推荐镜像

更多
  • DNS