【Spring源码】IOC实现-XmlBeanFactory

前言

首先,我们来了解下容器加载的类图:

在这里插入图片描述
  通过类图,我们可以看出BeanFactory是Spring容器的顶层接口。
  在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的,它的作用是:实例化、定位。配置引用程序中的对象及建立这些对象间的依赖。通过类图,我们也可以看出它是一个接口,具体实现包括:DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等。
  XmlBeanFactory 用于 Spring容器对 Xml 文件的加载,是容器的基础。本篇博文主要从源码角度来讲Xml文件的加载过程。

过程简要

Xml 文件加载过程如下:

  1. 把配置文件封装成Resource类型
  2. 加载整个资源
     2.1 封装资源文件
     2.2 获取输入流
     2.3 加载bean的核心
      2.3.1 获取对XML文件的验证模式
      2.3.2 加载XML文件,并得到对应的Document
      2.3.3 根据返回的Document注册Bean对象

源码解析

测试类准备(JDK1.8)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="myTestBean" class="MyTestBean"/>

</beans>
import lombok.Data;

@Data
public class MyTestBean {
    private String testStr ="testStr";
}
public class BeanFactoryTest {
    @Test
    public void testSimpleLoad(){
        BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("test.xml"));
        MyTestBean bean= (MyTestBean) beanFactory.getBean("myTestBean");
            Assert.assertEquals("testStr",bean.getTestStr());
    }
}

调试过程中我们可以以

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

为入口来了解Xml文件加载过程。

XmlBeanFactory 初始化时序图

在这里插入图片描述

过程代码解析

这一过程主要分两步:

  1. 把配置文件封装成Resource类型

Resource 接口封装了所有 Spring 内部用到的底层资源: File、URL、Classpath 等
(1)定义了 3 个判断当前资源状态的方法:存在性(exists )、可读性( isReadable)、是否处于打开状态( isOpen )。
(2)提供了不同资源到 URL 、URI、File 类型的转换,以及获取 lastModified 属性、文件名(不带路径信息的文件名, getFilename())的方法 。
(3)为了便于操作, 提供了基于当前资源创建一个相对资源的方法: createRelative()。
(4)在错误处理中需要详细地打印出错的资源文件,因而提供了 getDescription()方法用来在错误处理中打印信息 。
(5)对不同来源的资源文件都有相应的 Resource 实现:文件( FileSystemResource ) 、 Classpath资源( ClassPathResource )、 URL 资源( UrlResource )、 InputStream 资源( InputStreamResource )、Byte 数组( ByteArrayResource )等

总之,Resource接口用于对所有资源文件统一处理。

  1. 加载整个资源
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
   super(parentBeanFactory);
   this.reader.loadBeanDefinitions(resource);
}

loadBeanDefinitions的时序图
在这里插入图片描述
这个时序图主要体现了三大流程:
1、封装资源文件:对参数Resource使用EncodedResource类进行封装

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
   return loadBeanDefinitions(new EncodedResource(resource));
}

2、获取输入流:从 Resource 中获取对应的 InputStream ,构造 InputSource
主要逻辑:getReader() --> 设置了编码属性则使用该编码作为输入流编码。

public Reader getReader() throws IOException {
   if (this.charset != null) {
      return new InputStreamReader(this.resource.getInputStream(), this.charset);
   }
   else if (this.encoding != null) {
      return new InputStreamReader(this.resource.getInputStream(), this.encoding);
   }
   else {
      return new InputStreamReader(this.resource.getInputStream());
   }
}

3、用前两步的结果(构造的InputSource实例和Resource实例)来进行数据准备

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
   //断言:encodeResource不能为null,若为null则抛异常
   Assert.notNull(encodedResource, "EncodedResource must not be null");
   if (logger.isInfoEnabled()) {
      logger.info("Loading XML bean definitions from " + encodedResource.getResource());
   }
   
   //通过属性类记录已经加载的资源
   Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
   if (currentResources == null) {
      currentResources = new HashSet<>(4);
      this.resourcesCurrentlyBeingLoaded.set(currentResources);
   }
   if (!currentResources.add(encodedResource)) {
      throw new BeanDefinitionStoreException(
            "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
   }
   try {
       //从encodedResource中获取已经封装的Resource对象并在此从Resource中获取其中的inputStream
      InputStream inputStream = encodedResource.getResource().getInputStream();
      try {
         //InputSource这个类不来自于Spring,它的全路径是org.xml.sax.InputSource
         InputSource inputSource = new InputSource(inputStream);
         if (encodedResource.getEncoding() != null) {
            inputSource.setEncoding(encodedResource.getEncoding());
         }
         //核心逻辑
         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();
      }
   }
}

核心逻辑

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
   try {
      int validationMode = getValidationModeForResource(resource);
      Document doc = this.documentLoader.LoadDocument(inputSource, getEntityResolver(),validationMode,isNamespaceAware());
      return registerBeanDefinitions(doc, resource);
   }
   catch (BeanDefinitionStoreException ex) {
      throw ex;
   }
   catch (SAXParseException ex) {
      throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
   }
   catch (SAXException ex) {
      throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex);
   }
   catch (ParserConfigurationException ex) {
      throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex);
   }
   catch (IOException ex) {
      throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex);
   }
   catch (Throwable ex) {
      throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex);
   }
}

(1)获取对XML文件的验证模式

是否指定验证模式 --> 以此决定检测策略为指定模式还是自动模式(判断是否包含DOCTYPE,来判断文件格式时 DTD 还是XSD)

 int validationMode = getValidationModeForResource(resource);
protected int getValidationModeForResource(Resource resource) {
   int validationModeToUse = getValidationMode();
   //手动指定-->使用指定的验证模式
   if (validationModeToUse != VALIDATION_AUTO) {
      return validationModeToUse;
   }
   //未指定-->使用自动检测
   int detectedMode = detectValidationMode(resource);
   if (detectedMode != VALIDATION_AUTO) {
      return detectedMode;
   }
   return VALIDATION_XSD;
}
public int detectValidationMode(InputStream inputStream) throws IOException {
   BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
   try {
      boolean isDtdValidated = false;
      String content;
      while ((content = reader.readLine()) != null) {
         content = consumeCommentTokens(content);
          // 如果读取的行是空,或者是注释 --> 略过
         if (this.inComment || !StringUtils.hasText(content)) {
            continue;
         }
         if (hasDoctype(content)) {
            isDtdValidated = true;
            break;
         }
         //读取到<开始符号,验证模式一定会在开始符号之前
         if (hasOpeningTag(content)) {
            // End of meaningful data...
            break;
         }
      }
      return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
   }
   catch (CharConversionException ex) {
      return VALIDATION_AUTO;
   }
   finally {
      reader.close();
   }
}
private boolean hasOpeningTag(String content) {
   if (this.inComment) {
      return false;
   }
   int openTagIndex = content.indexOf('<');
   return (openTagIndex > -1 && (content.length() > openTagIndex + 1) && Character.isLetter(content.charAt(openTagIndex + 1)));
}

(2)加载XML文件,并得到对应的Document

Document doc = this.documentLoader.LoadDocument(inputSource, getEntityResolver(),validationMode,isNamespaceAware());

XmlBeanFactoryReader 类对于文档读取委托给了 DocumentLoader 去执行, 这里的 DocumentLoader是个接口,而真正调用的是 DefaultDocumentLoader

@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
      ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
   // 创建DocumentBuilderFactory
   DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);  
   if (logger.isDebugEnabled()) {
      logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
   }
   // 创建DocumentBuilder
   DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); 
   // 解析inputSource
   return builder.parse(inputSource);  
}

(3)根据返回的Document注册Bean信息(提取root)

return registerBeanDefinitions(doc, resource);
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
   //实例化BeanDefinitionDocumentReader
   BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
   //记录统计前BeanDefinition的加载个数
   int countBefore = getRegistry().getBeanDefinitionCount();
   //加载及注册bean
   documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
   //记录本次加载的BeanDefinition个数
   return getRegistry().getBeanDefinitionCount() - countBefore;
}

createBeanDefinitionDocumentReader:
提取root,以便再次将root作为参数继续BeanDefinition的注册

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
   this.readerContext = readerContext;
   logger.debug("Loading bean definitions");
   Element root = doc.getDocumentElement();
   doRegisterBeanDefinitions(root);
}

解析核心

protected void doRegisterBeanDefinitions(Element root) {
   //专门处理解析
   BeanDefinitionParserDelegate parent = this.delegate;
   this.delegate = createDelegate(getReaderContext(), root, parent);

   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);
         if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
            if (logger.isInfoEnabled()) {
               logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource());
            }
            return;
         }
      }
   }

   //解析前处理,留给子类实现
   preProcessXml(root);
   //解析并注册BeanDefinition
   parseBeanDefinitions(root, this.delegate);
   //解析后处理,留给子类实现
   postProcessXml(root);

   this.delegate = parent;
}
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)) {
                //对bean的处理
               parseDefaultElement(ele, delegate);
            }
            else {
               delegate.parseCustomElement(ele);
            }
         }
      }
   } else {
      //对bean的处理
      delegate.parseCustomElement(root);
   }
}

对XML文件的各节点分类解析

    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
        if (delegate.nodeNameEquals(ele, "import")) {
            this.importBeanDefinitionResource(ele);
        } else if (delegate.nodeNameEquals(ele, "alias")) {
            this.processAliasRegistration(ele);
        } else if (delegate.nodeNameEquals(ele, "bean")) {
            this.processBeanDefinition(ele, delegate);
        } else if (delegate.nodeNameEquals(ele, "beans")) {
            this.doRegisterBeanDefinitions(ele);
        }
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值