3、Spring读取XML封装BeanDefinition的大概流程

前言:

上篇文章介绍了Resource接口并引出了Spring IOC 工厂的核心内容

此片文章将讲解Spring底层 如何做到通过XmlBeanDefinitionReader 读取 XML 并封装成BeanDefinition的大概流程。

Spring读取XML封装BeanDefinition的流程

调用XmlBeanFactory构造方法。

Spring读取XML并封装成BeanDefinition是如下这行代码完成的:

BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));

创建XmlBeanFactory工厂时,调用了XmlBeanFactory这个类的构造方法。

XmlBeanFactory源码:

public class XmlBeanFactory extends DefaultListableBeanFactory {
    
   private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
   
   //上面new XmlBeanFactory(resource对象) 会先调用此构造方法;
   public XmlBeanFactory(Resource resource) throws BeansException {
       //此方法内部又调用了这个类的重载方法,
      this(resource, null);
   }
   
    //最终会调用此构造方法。
   public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
       //设置父容器
      super(parentBeanFactory);
      
      //此处是最为核心的代码,this.reader 就是这个类的成员变量 XmlBeanDefinitionReader 
      //通过调用 XmlBeanDefinitionReader 的 loadBeanDefinitions 方法,来读取resource,也就是读取xml配置文件。
      this.reader.loadBeanDefinitions(resource);
   }
}

这个类中包含了两个重载的构造方法,最终会调用第二个构造。

第二个构造需要两个参数:

  1. resource:资源信息
  2. parentBeanFactory:父工厂,Spring中允许在一个工程中 有多个Spring工厂同时出现 ,情况非常少见,这种情况在Springmvc中就出现过,其中就有如下两个容器:
    1. DispatcherServlet 创建的子容器:childFactory
    2. ContextLoaderListener 创建的子容器:rootFactory

所以,spring在设计的过程中通常会兼顾父容器的情况,但是在咱们用spring开发的过程当中和阅读源码的过程中基本可以忽略父容器的情况。

我们接着来解析this.reader.loadBeanDefinitions(resource)内部的源码:

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
    //1.先调用到此方法
    @Override
    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        //因为spring考虑到resource文件是需要编码的,所以此处对resource包了一层,
        //但是我们现在默认的开发过程当中,不需要进行编码,所以这个编码操作没有任何作用。此处可以把这个EncodedResource,等同于resource来看。
       return loadBeanDefinitions(new EncodedResource(resource));
    }
    
    //2.接着调用这个,这个才是真正的读取操作。
    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集合中。不重要
       Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
       if (!currentResources.add(encodedResource)) {
          throw new BeanDefinitionStoreException(
                "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
       }

        //下面的try块的部分是最为核心的。
        //try块的小括号中先获取了resource对象,接着获取了InputStream文件流信息,
        //因为咱们传的是ClassPathResource,而ClassPathResource间接性的实现了Resource接口,Resource接口又继承了InputStreamSource接口,InputStreamSource接口中定义了getInputStream()方法
        //所以咱们可以直接拿到InputStream文件流信息。
       try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
           //此处的核心是:将获取的inputStream输入流进行解析,然后封装成一个解析的工具类InputSource
          InputSource inputSource = new InputSource(inputStream);
          //因为encodedResource默认编码是null,所以这个if语句为false,就不会执行里面的代码。
          if (encodedResource.getEncoding() != null) {
             inputSource.setEncoding(encodedResource.getEncoding());
          }
          
          /**
           * 此处是解析的核心,有两个参数:
           * inputSource:解析xml解析所需要的工具类
           * encodedResource.getResource():需要解析的xml文件
           * 通过如下解析,最终就能帮我们生成最终的BeanDefinition
           */
          return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
          
          
       }catch (IOException ex) {
           throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
       } finally {
           currentResources.remove(encodedResource);
           if (currentResources.isEmpty()) {
              this.resourcesCurrentlyBeingLoaded.remove();
           }              
       }
    }
}

接下来我们来看 doLoadBeanDefinitions 方法里面的源码:

此处省略部分无用的代码(异常的处理,日志的输出等)

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
      throws BeanDefinitionStoreException {

   try {
      /*
      * 解析xml,将其封装成Document对象。
      * 此处需要两个参数:
      * 1.inputSource:解析xml文件的工具类
      * 2.resource:xml文件
      * 注意:这个Document对象是针对于解析xml后所封装的对象的,document并不是spring的对象,所以spring并不需要这个对象
      * 所有后期spring还会将这个对象再次转换成BeanDefinition对象
      * */
      Document doc = doLoadDocument(inputSource, resource);

      /**
       * 这个方法就是将document对象转换成BeanDefinition,这个方法也是最重要的方法
       * 此处需要两个参数:
       * 1.doc:根据xml所封装的document对象
       * 2.resource:xml文件
       * spring将document对象转换成BeanDefinition对象,返回的是注册的个数。
       */
      int count = registerBeanDefinitions(doc, resource);
      return count;
   }
}

接着展开讲 int count = registerBeanDefinitions(doc, resource) 方法的源码:

//这里是注册BeanDefinition的核心:那么咱们继续找这块核心代码的核心。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
   BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
   //在注册新的BeanDefinition之前,先统计目前这个注册器注册了多少个BeanDefinition
   int countBefore = getRegistry().getBeanDefinitionCount();
   //此处是核心:这里开始根据document对象注册BeanDefinition
   documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
   //计算本次注册的BeanDefinition数量:获取注册完之后注册器里的BeanDefinition个数,减去注册之前已经存在于注册器中的BeanDefinition个数。
   return getRegistry().getBeanDefinitionCount() - countBefore;
}

接着展开讲 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)) 方法的源码:

//先调用这个方法
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
   this.readerContext = readerContext;
   doRegisterBeanDefinitions(doc.getDocumentElement());
}


//接着调用此类的内部方法doRegisterBeanDefinitions
protected void doRegisterBeanDefinitions(Element root) {
   
   BeanDefinitionParserDelegate parent = this.delegate;
   this.delegate = createDelegate(getReaderContext(), root, parent);
   
    //这块的代码用处不大,原因自己看if语句中的注释
   if (this.delegate.isDefaultNamespace(root)) {
       //此处开始处理xml文件里的标签了,root代表xml文件的根标签<beans>
       //root.getAttribute(PROFILE_ATTRIBUTE) 这个处理的是<beans profile=""></beans>中的profile属性
       //profile是spring在编写xml的时候是可以定义环境的,例如:开发环境(dev),生产环境(production),到时可以根据更改此属性配置,达到切换环境的目的
       //此处目前开发用的不多  不用关注,所以相应的 下面的 if 语句中的内容也不用关注。
      String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
      //此if不用关注
      if (StringUtils.hasText(profileSpec)) {
         String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
               profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
         
         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;
         }
      }
   }

   preProcessXml(root);
   
   //这里才是真正的注册BeanDefinition的核心方法
   //root是根标签<bean>,delegate是相应的注册器
   parseBeanDefinitions(root, this.delegate);
   
   postProcessXml(root);

   this.delegate = parent;
}

接着展开讲 parseBeanDefinitions(root, this.delegate) 方法的源码:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
   if (delegate.isDefaultNamespace(root)) {
       //此处获取root的子节点list,然后在for循环中挨个处理
      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块中的parseDefaultElement(ele, delegate)方法 和 else块中的delegate.parseCustomElement(ele)方法
            if (delegate.isDefaultNamespace(ele)) {
                //这个方法的目的是:解析基本标签,<bean  id="" class="" parent="">
                //所以这里是解析的重点
               parseDefaultElement(ele, delegate);
            } else {
                //这个方法的目的是:解析自定义标签的
               delegate.parseCustomElement(ele);
            }
         }
      }
   }
   else {
      delegate.parseCustomElement(root);
   }
}

基本标签:

<bean id="" class="" scope="" parent="" init-method="">
    <property name  value
</bean>

<bean id="" class="" scope="" parent="" init-method="">
    <construt-arg>
 </bean>

自定义标签,新的命名空间标签:

<context:propertyplace-holder
<context:component-scan
..
<tx:annotation-driven
<mvc:annotation-drvent

<aop:config

接着展开讲 parseDefaultElement(ele, delegate) 解析基本标签的方法源码:

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    //如果是import标签:import标签的作用是可以在此xml里引入别的xml配置文件
   if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
      importBeanDefinitionResource(ele);
   }
   //如果是alias标签:alias标签的作用是定义别名的标签
   else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
      processAliasRegistration(ele);
   }
   //如果是bean标签:此处是解析成BeanDefinition的核心重点
   else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
      processBeanDefinition(ele, delegate);
   }
    //如果是beans标签
   else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
      doRegisterBeanDefinitions(ele);
   }
}

接着展开讲 processBeanDefinition(ele, delegate) 方法的源码:

//此方法需要两个参数:1.在之前解析的子节点element,2.解析器
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    //此处将element对象解析成BeanDefinitionHolder对象,这个BeanDefinitionHolder就相当于对BeanDefinition做了一层包装。
   BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
   if (bdHolder != null) {
       //如果bean标签中嵌套了自定义标签,那么就会经过此方法解析,对element进行再次解析,并封装成最终的bdHolder
      bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
      try {
         //此处是注册BeanDdfinition,用BeanDefinitionReaderUtils工具类进行注册,并存储在一个Map中。
         BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
      }
      catch (BeanDefinitionStoreException ex) {
         getReaderContext().error("Failed to register bean definition with name '" +
               bdHolder.getBeanName() + "'", ele, ex);
      }
      // 此处是发布事件:整个spring容器注册完成之后,发布事件,通知spring容器,spring监听此事件,如果监听到了,进行后去处理
      //但是此方法是个空方法,这是spring容器给开发者留的一个扩展点。
      getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
   }
}

总结:

经过上述的一系列流程,大家已经基本了解了spring是如何读取xml文件,如何解析xml文件,并到最后看到了真正解析各种标签的代码。

此文章就先到这里,在下一篇中,我会详细讲解怎样将element对象解析成BeanDefinitionHolder对象的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值