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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 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);
   }
}
相关文章
|
19天前
|
监控 网络协议 Java
Tomcat源码解析】整体架构组成及核心组件
Tomcat,原名Catalina,是一款优雅轻盈的Web服务器,自4.x版本起扩展了JSP、EL等功能,超越了单纯的Servlet容器范畴。Servlet是Sun公司为Java编程Web应用制定的规范,Tomcat作为Servlet容器,负责构建Request与Response对象,并执行业务逻辑。
Tomcat源码解析】整体架构组成及核心组件
|
4天前
|
存储 缓存 Java
什么是线程池?从底层源码入手,深度解析线程池的工作原理
本文从底层源码入手,深度解析ThreadPoolExecutor底层源码,包括其核心字段、内部类和重要方法,另外对Executors工具类下的四种自带线程池源码进行解释。 阅读本文后,可以对线程池的工作原理、七大参数、生命周期、拒绝策略等内容拥有更深入的认识。
什么是线程池?从底层源码入手,深度解析线程池的工作原理
|
4天前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
|
8天前
|
开发工具
Flutter-AnimatedWidget组件源码解析
Flutter-AnimatedWidget组件源码解析
|
17天前
|
设计模式 存储 人工智能
深度解析Unity游戏开发:从零构建可扩展与可维护的游戏架构,让你的游戏项目在模块化设计、脚本对象运用及状态模式处理中焕发新生,实现高效迭代与团队协作的完美平衡之路
【9月更文挑战第1天】游戏开发中的架构设计是项目成功的关键。良好的架构能提升开发效率并确保项目的长期可维护性和可扩展性。在使用Unity引擎时,合理的架构尤为重要。本文探讨了如何在Unity中实现可扩展且易维护的游戏架构,包括模块化设计、使用脚本对象管理数据、应用设计模式(如状态模式)及采用MVC/MVVM架构模式。通过这些方法,可以显著提高开发效率和游戏质量。例如,模块化设计将游戏拆分为独立模块。
41 3
|
16天前
|
JavaScript 前端开发 API
Javaweb之javascript的BOM对象的详细解析
BOM为Web开发提供了强大的API,允许开发者与浏览器进行深入的交互。合理使用BOM中的对象和方法,可以极大地增强Web应用的功能性和用户体验。需要注意的是,BOM的某些特征可能会在不同浏览器中表现不一致,因此在开发过程中需要进行仔细的测试和兼容性处理。通过掌握BOM,开发者能够制作出更丰富、更动态、更交互性的JavaWeb应用。
12 1
|
17天前
|
开发者 测试技术 Android开发
Xamarin 开发者的五大常见问题及解决方案:从环境搭建到性能优化,全面解析高效跨平台应用开发的技巧与代码实例
【8月更文挑战第31天】Xamarin 开发者常遇问题及解决方案覆盖环境搭建至应用发布全流程,助新手克服技术难关。首先需正确安装配置 Visual Studio 及 Xamarin 支持,设置 iOS/Android 测试环境。利用 Xamarin.Forms 和 XAML 实现高效跨平台开发,共享 UI 和业务逻辑代码。针对性能优化,采取减少 UI 更新、缓存计算结果等措施,复杂问题则借助 Xamarin Profiler 分析。
30 0
|
18天前
|
数据采集 弹性计算 供应链
阿里云服务器付费模式:按量付费、包年包月和抢占式实例全解析
阿里云服务器提供包年包月、按量付费与抢占式实例三种付费模式。包年包月为预付费,适合长期稳定使用,价格更优惠并支持备案。按量付费则为后付费模式,按小时结算,适合短期或访问量波动大的场景,但不支持备案。抢占式实例基于按量付费,价格更低(最多节省90%),适用于无状态应用,如临时测试或可弹性伸缩的Web服务,但存在被系统释放的风险,同样不支持备案。根据具体需求选择合适的付费模式能够有效降低成本并提高效率。
36 0
|
1月前
|
存储 NoSQL Redis
redis 6源码解析之 object
redis 6源码解析之 object
53 6
|
26天前
|
测试技术 Python
python自动化测试中装饰器@ddt与@data源码深入解析
综上所述,使用 `@ddt`和 `@data`可以大大简化写作测试用例的过程,让我们能专注于测试逻辑的本身,而无需编写重复的测试方法。通过讲解了 `@ddt`和 `@data`源码的关键部分,我们可以更深入地理解其背后的工作原理。
22 1

热门文章

最新文章

推荐镜像

更多