关于读书笔记:Spring源码之容器的基本实现

4次阅读

共计 15936 个字符,预计需要花费 40 分钟才能阅读完成。

开篇

本文次要基于 SpringFramework5.2.0.RELEASE 版本,源码的下载步骤在别的文章中曾经讲过,这里就不再赘述。

容器的根本用法

咱们先创立一个简略的示例来看一下容器的根本用法。

创立一个简略的 Java Bean。

/**
 * @author 神秘杰克
 * 公众号: Java 菜鸟程序员
 * @date 2022/3/15
 * @Description 简略的 bean 实例
 */
public class MyTestBean {

   private String testStr = "testStr";

   public String getTestStr() {return testStr;}

   public void setTestStr(String testStr) {this.testStr = testStr;}
}

创立一个简略 Spring 配置文件。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
   <bean id="myTestBean" class="cn.jack.MyTestBean"/>
</beans>

ok,编写一个测试类进行测试。

/**
 * @author 神秘杰克
 * 公众号: Java 菜鸟程序员
 * @date 2022/3/15
 * @Description 测试类
 */
@SuppressWarnings("deprecation")
public class BeanFactoryTest {

   @Test
   public void testSimpleLoad(){final BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
      final MyTestBean myTestBean = (MyTestBean) beanFactory.getBean("myTestBean");
      assertEquals("testStr",myTestBean.getTestStr());
   }

}

运行之后能够看到执行胜利,示例就这么简略。

平时咱们并不会间接应用 BeanFactory,而是应用 ApplicationContext,这里只是为了之后能够更好地剖析 Spring 原理。

功能分析

这部分代码实现的性能如下:

  • 读取配置文件 beanFactoryTest.xml
  • 依据配置文件中的配置找到对应类的配置,并实例化
  • 调用实例化后的实例

依照这些形容,咱们能够理解须要至多三个类来实现。

  • ConfigReader:用于读取及验证配置文件,随后放到内存中
  • ReflectionUtil:依据配置文件内容,进行反射实例化,也就是咱们配置文件中的<bean id="myTestBean" class="cn.jack.MyTestBean"/>
  • App:用于实现整个逻辑的串联

依照原始的思维形式,整个过程就是这样,然而这么优良的 Spring 框架的构造组成是什么样子?

Spring 的层级架构

咱们首先尝试梳理 Spring 的框架结构,用全局的角度理解 Spring 的形成。

Beans 包的层级架构

实现方才的示例,咱们次要应用的就是 org.springframework.beans.jar。

剖析源码前,咱们首先理解两个外围类。

1.DefaultListableBeanFactory

咱们方才应用的 XmlBeanFactory 继承自 DefaultListableBeanFactory,而 DefaultListableBeanFactory 是整个加载的外围局部, 是 Spring 注册及加载 bean 的默认实现,而在 XmlBeanFactory 中应用了自定义的 XML 读取器XmlBeanDefinitionReader,实现了个性化的 BeanDefinitionReader 读取。DefaultListableBeanFactory 继承了 AbstractAutoWireCapableBeanFactory 并实现了 ConfigurableListableBeanFactory 以及 BeanDefinitionRegistry 接口。

从该类图咱们能够清晰理解 DefaultListableBeanFactory 的脉络,咱们先简略理解一下各个类的作用。

  • AliasRegistry:定义对 alias 的简略增删改操作
  • SimpleAliasRegistry:次要应用 map 作为 alias 缓存,并对接口 AliasRegistry 进行实现
  • SingletonBeanRegistry:定义对单例的注册和获取
  • BeanFactory:定义获取 bean 和 bean 的各种属性
  • DefaultSingletonBeanRegistry:对接口 SingletonBeanRegistry 的各个函数实现
  • HierarchicalBeanFactory:继承 BeanFactory,在 BeanFactory 定义的性能根底上减少了对 parentFactory 的反对
  • BeanDefinitionRegistry:定义对 BeanDefinition 的各种增删改操作
  • FactoryBeanRegistrySupport:在 DefaultSingletonBeanRegistry 根底上减少了对 FactoryBean 的非凡解决性能
  • ConfigurableBeanFactory:提供配置 Factory 的各种办法
  • ListableBeanFactory:依据各种条件获取 bean 的配置清单
  • AbstractBeanFactory:综合了 FactoryBeanRegistrySupport、ConfigurableBeanFactory 的性能
  • AutowireCapableBeanFactory:提供创立 bean、主动注入、初始化以及利用 bean 的后处理器
  • AbstractAutowireCapableBeanFactory:综合 AbstractBeanFactory 性能并对接口 AutowireCapableBeanFactory 进行实现
  • ConfigurableListableBeanFactory:BeanFactory 配置清单,指定疏忽类型及接口等
  • DefaultListableBeanFactory:综合下面所有性能,次要对 bean 注册后的解决

XmlBeanFactory 继承 DefaultListableBeanFactory 进行了扩大,次要用于从 XML 中读取 BeanDefinition,对于注册和获取 bean 都是应用从父类 DefaultListableBeanFactory 继承的办法中实现,与父类不同的是 XmlBeanFactory 减少了 XmlBeanDefinitionReader 类型的 reader 属性,进行对资源文件的读取和注册。

2.XmlBeanDefinitionReader

XML 配置文件的读取是 Spring 中重要的性能,咱们能够从 XmlBeanDefinitionReader 中梳理一下资源文件读取、解析及注册的大抵脉络。

咱们首先看一下各个类的性能:

  • ResourceLoader:定义资源加载器,次要利用于依据给定的资源文件地址返回对应的 Resource
  • BeanDefinitionReader:次要定义资源文件读取并转换为 BeanDefinition 的各个性能
  • EnvironmentCapable:定义获取 Environment 办法
  • DocumentLoader:定义从资源文件加载到转换为 Document 的性能
  • AbstractBeanDefinitionReader:对 EnvironmentCapable、BeanDefinitionReader 类定义的办法进行实现
  • BeanDefinitionDocumentReader:定义读取 Document 并注册 BeanDefinition 性能
  • BeanDefinitionParserDelegate:定义解析 Element 的各种办法

通过下面的剖析,咱们能够梳理出 XML 配置文件读取的大略流程。

  1. 通过继承自 AbstractBeanDefinitionReader 中的办法,来应用 ResourLoader 将资源文件门路转换为对应的 Resource 文件
  2. 通过 DocumentLoader 对 Resource 文件进行转换,将 Resource 文件转换为 Document 文件
  3. 通过实现接口 BeanDefinitionDocumentReader 的 DefaultBeanDefinitionDocumentReader 类对 Document 进行解析,并应用 BeanDefinitionParserDelegate 对 Element 进行解析

容器的根底 XmlBeanFactory

在有了对 Spring 容器的大抵理解后,咱们接下来剖析上面代码的实现。

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

通过时序图咱们能够看到首先调用了 ClassPathResource 的构造函数来结构 Resource 资源文件的实例对象,之后的资源解决就能够用 Resource 提供的服务来操作了,之后就能够进行对 XmlBeanFactory 进行初始化了,接下来咱们看一下 Resource 资源是如何封装的。

配置文件封装

首先读取文件是通过 ClassPathResource 进行封装的,比方new ClassPathResource("beanFactoryTest.xml"),那么 ClassPathResource 实现了什么性能?

在 Java 中,将不同起源的资源形象为 URL,而后注册不同的 handler(URLStreamHandler)来解决不同资源的读取逻辑,然而 URL 没有默认定义绝对 Classpath 或 ServletContext 等资源的 handler,尽管能够通过注册本人的 URLStreamHandler 来解析特定的 URL 前缀协定,然而这须要理解 URL 的实现机制,而且 URL 也没有提供根本的办法(比方资源是否可读、是否存在等)。因此在 Spring 中对其外部应用到的资源实现了本人的形象构造,Resource 接口封装底层资源。

public interface InputStreamSource {InputStream getInputStream() throws IOException;

}
public interface Resource extends InputStreamSource {boolean exists();


   default boolean isReadable() {return exists();
   }


   default boolean isOpen() {return false;}

   default boolean isFile() {return false;}


   URL getURL() throws IOException;

   URI getURI() throws IOException;

   File getFile() throws IOException;

   default ReadableByteChannel readableChannel() throws IOException {return Channels.newChannel(getInputStream());
   }

   long contentLength() throws IOException;

   long lastModified() throws IOException;

   Resource createRelative(String relativePath) throws IOException;

   @Nullable
   String getFilename();

   String getDescription();}

InputStreamSource 中只有一个办法 getInputStream(),返回一个新的 InputStream 对象。

Resource 接口形象了 Spring 外部应用到的底层资源,File、URL、Classpath 等。定义了 4 个判断资源状态的办法:exists()、isReadable()、isOpen()、isFile(),另外,Resource 接口也提供了不同资源到 URL、URI、File 类型的转换。

createRelative()办法能够基于以后资源创立一个绝对资源的办法。

getDescription()办法用来在错误处理中打印信息。

大抵理解了 Spring 中将配置文件封装为 Resource 类型的实例办法后,咱们持续看 XmlBeanFactory 的初始化过程,咱们这里通过应用 Resource 实例作为结构函数参数的办法。

public XmlBeanFactory(Resource resource) throws BeansException {this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {super(parentBeanFactory);
   this.reader.loadBeanDefinitions(resource);
}

下面的办法中 this.reader.loadBeanDefinitions(resource); 就是资源加载的真正实现,也是剖析的重点之一

咱们下面的时序图 3.1 就是这里实现的。在调用到该办法之前,咱们还要调用父类结构器进行初始化。

咱们间接跟踪到父类 AbstractAutowireCapableBeanFactory 的构造函数中:

public AbstractAutowireCapableBeanFactory() {super();
   ignoreDependencyInterface(BeanNameAware.class);
   ignoreDependencyInterface(BeanFactoryAware.class);
   ignoreDependencyInterface(BeanClassLoaderAware.class);
}

ignoreDependencyInterface 的次要性能是 疏忽给定接口的主动拆卸性能。为什么要这样做?

比方:当 A 中有属性 B,当 Spring 在获取 A 的 bean 的时候如果 B 还没初始化,那么 Spring 会主动初始化 B,然而在某些状况下,B 不会被初始化,其中一个状况就是 B 实现了 BeanNameAware 接口。

Spring 中这样介绍的:主动拆卸时疏忽给定的依赖接口,典型利用就是通过其余形式解析 Application 上下文注册依赖,相似于 BeanFactory 通过 BeanFactoryAware 进行注入或者 ApplicationContext 通过 ApplicationContextAware 进行注入。

加载 Bean

回到方才,咱们在 XmlBeanFactory 构造函数中调用了 this.reader.loadBeanDefinitions(resource); 这句代码就是整个资源加载的入口,咱们看一下这个办法的时序图。

咱们尝试梳理一下处理过程:

  1. 应用 EncodedResource 类对参数 Resource 资源文件进行封装
  2. 从 Resource 中获取对应的 InputStream,随后结构 InputSource
  3. 随后应用结构的 InputSource 实例和 Resource 实例持续调用 doLoadBeanDefinitions 办法

接下来看一下 loadBeanDefinitions 办法具体实现过程。

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

首先咱们看一下 EncodedResource 的作用是什么,通过名字能够看进去是对资源文件进行编码解决,次要逻辑就在其中的 getReader() 办法中,如果设置了编码属性,Spring 会应用相应的编码作为输出流的编码。

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());
   }
}

该办法结构了一个 InputStreamReader。当结构完 EncodedResource 之后,调用了 loadBeanDefinitions 重载办法。

该办法外部就是真正的数据筹备阶段了。

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);
   }
        //resourcesCurrentlyBeingLoaded 是一个 ThreadLocal,外面寄存着 Resource 类的 set 汇合
   Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
   if (currentResources == null) {currentResources = new HashSet<>(4);
      this.resourcesCurrentlyBeingLoaded.set(currentResources);
   }
   // 如果 set 中已有这个元素则返回 false 并抛出异样
   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 {
         // 筹备解析 xml 文件,全门路为 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 {
       // 资源加载结束,移除该 Resource
      currentResources.remove(encodedResource);
      // 如果没有其余资源了,则 remove
      if (currentResources.isEmpty()) {this.resourcesCurrentlyBeingLoaded.remove();
      }
   }
}

该办法首先将传入的 Resource 参数进行封装,目标是为了思考到 Resource 可能存在编码要求的状况,其次,通过 SAX 读取 XML 文件的形式来创立 InputSource 对象,最初将参数传入到 外围解决局部doLoadBeanDefinitions(inputSource,encodedResource.getResource())

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

   try {Document doc = doLoadDocument(inputSource, resource);
      int count = registerBeanDefinitions(doc, resource);
      if (logger.isDebugEnabled()) {logger.debug("Loaded" + count + "bean definitions from" + resource);
      }
      return count;
   }
   catch (BeanDefinitionStoreException ex) {throw ex;}
   // ... 省略 catch
}

该办法次要做三件事:

  • 获取对 XML 文件的验证模式
  • 加载 XML 文件,失去对应的 Document
  • 依据返回的 Document 注册 Bean 信息

咱们一步步看,从 XML 验证模式开始。

获取 XML 的验证模式

比拟罕用的验证 XML 正确性有两种:DTD 和 XSD。

DTD(Document Type Definition)即文档类型定义,是一种 XML 束缚模式语言,是 XML 文件的验证机制

DTD 即文档类型定义,是一种 XML 束缚模式语言,是 XML 文件的验证机制, 属于 XML 文件组成的一部分。
DTD 是一种保障 XML 文档格局正确的无效办法,能够通过比拟 XML 文档和 DTD 文件来看文档是否符合规范,元素和标签应用是否正确。一个 DTD 文档蕴含:元素的定义规定,元素间关系的定义规定,元素可应用的属性,可应用的实体或符号规定。

DTD 和 XSD 相比:DTD 是应用非 XML 语法编写的。
DTD 不可扩大,不反对命名空间,只提供十分无限的数据类型。

XSD(XML Schemas Definition),即 XML Schema 语言,针对 DTD 的缺点有 W3C 在 2001 年推出。XML Schema 自身就是一个 XML 文档,应用的是 XML 语法,因而能够很不便地解析 XSD 文档。

绝对于 DTD,XSD 具备如下劣势:

  • XML Schema 基于 XML,没有专门的语法
  • XML Schema 能够像其余 XML 文件一样解析和解决
  • XML Schema 相比于 DTD 提供了更丰盛的数据类型
  • XML Schema 提供可扩大的数据模型
  • XML Schema 反对综合命名空间
  • XML Schema 反对属性组

在 Spring 源码中,基于 XML 文件配置 Bean 的验证模式,个别状况下是 XSD 模式。

验证模式的读取

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;
}

Spring 检测验证模式就是通过判断是否蕴含 DOCTYPE,蕴含就是 DTD,否则是 XSD。

获取 Document

通过验证模式筹备后,就能够进行 Document 加载了,这里的 documentLoader 是一个接口,真正实现是上面的 DefaultDocumentLoader。

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
         getValidationModeForResource(resource), isNamespaceAware());
}
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
      ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
      // 创立文档构建器工厂对象,并初始化一些属性
    // 如果验证模式为 XSD,那么强制反对 XML 名称空间,并加上 schema 属性
   DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
   if (logger.isTraceEnabled()) {logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
   }
   DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
   // 依照 XML 文档解析给定 inputSource 的内容,而后返回一个新的 DOM 对象
   return builder.parse(inputSource);
}

这段代码次要创立了一个 DocumentBuilderFactory 实例,再通过 DocumentBuilderFactory 创立了一个 DocumentBuilder,最初解析 inputSource 返回 Document 对象。

解析及注册 BeanDefinitions

咱们再回到 doLoadBeanDefinitions 办法,拿到 Document 对象后,咱们就能够注册 bean 对象,调用 registerBeanDefinitions 办法。

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

调用 registerBeanDefinitions 办法,抉择实现类为DeDefaultBeanDefinitionDocumentReader

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
   this.readerContext = readerContext;
   // 拿到了 xml 文档对象的根元素 并调用该办法
   doRegisterBeanDefinitions(doc.getDocumentElement());
}

doRegisterBeanDefinitions 开始真正地解析。

protected void doRegisterBeanDefinitions(Element root) {
    // 任何被嵌套的 <beans> 元素都会导致此办法的递归。为了正确的流传和保留 <beans> 的默认属性、// 放弃以后(父)代理的跟踪,它可能为 null
    // 为了可能回退,新的(子)代理具备父的援用,最终会重置 this.delegate 回到它的初始(父)援用。// 这个行为模仿了一堆代理,但实际上并不需要一个代理
   BeanDefinitionParserDelegate parent = this.delegate;
   // 代码(1)this.delegate = createDelegate(getReaderContext(), root, parent);
      // 默认名称空间是 "http://www.springframework.org/schema/beans"
   if (this.delegate.isDefaultNamespace(root)) {String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
     // 在 xml 配置文件中对 profile 的设置 辨别是生产环境还是线上环境
      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,并注册在工厂中,代码(2)parseBeanDefinitions(root, this.delegate);
   // 解析后处理,留给子类实现
   postProcessXml(root);

   this.delegate = parent;
}

咱们先看一下代码(1)的办法,该办法创立了一个 BeanDefinitionParserDelegate 对象,该对象是对 XML 中属性值解析的委派。

protected BeanDefinitionParserDelegate createDelegate(XmlReaderContext readerContext, Element root, @Nullable BeanDefinitionParserDelegate parentDelegate) {BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
   delegate.initDefaults(root, parentDelegate);
   return delegate;
}

咱们看下 BeanDefinitionParserDelegate 类的常量。

public class BeanDefinitionParserDelegate {

   public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";

   public static final String MULTI_VALUE_ATTRIBUTE_DELIMITERS = ",;";

   /**
    * Value of a T/F attribute that represents true.
    * Anything else represents false. Case seNsItive.
    */
   public static final String TRUE_VALUE = "true";

   public static final String FALSE_VALUE = "false";

   public static final String DEFAULT_VALUE = "default";

   public static final String DESCRIPTION_ELEMENT = "description";

   public static final String AUTOWIRE_NO_VALUE = "no";

   public static final String AUTOWIRE_BY_NAME_VALUE = "byName";

   public static final String AUTOWIRE_BY_TYPE_VALUE = "byType";

   public static final String AUTOWIRE_CONSTRUCTOR_VALUE = "constructor";

   public static final String AUTOWIRE_AUTODETECT_VALUE = "autodetect";

   public static final String NAME_ATTRIBUTE = "name";

   public static final String BEAN_ELEMENT = "bean";

   public static final String META_ELEMENT = "meta";

   public static final String ID_ATTRIBUTE = "id";

   public static final String PARENT_ATTRIBUTE = "parent";

   //...
}

咱们发现 Spring 配置文件的属性全都在这里 当初咱们晓得 BeanDefinitionParserDelegate 对象的确是来对 XML 配置文件的解析后,持续回到 createDelegate 办法,创立了 BeanDefinitionParserDelegate 对象后,还执行了 initDefaults 办法,来初始化一些默认值。

解析并注册 BeanDefinition

当初咱们回到代码(2),进入 parseBeanDefinitions 办法。

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
   // 对 beans 的解决,默认名称空间是 "http://www.springframework.org/schema/beans"
   if (delegate.isDefaultNamespace(root)) {
     // 获取根元素下的子 Node
      NodeList nl = root.getChildNodes();
      for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);
         if (node instanceof Element) {
           // 拿到了 <beans> 下的子标签
            Element ele = (Element) node;
            if (delegate.isDefaultNamespace(ele)) {
               // 如果该标签属于 beans 的名称空间,则进入这个办法
               //xmlns="http://www.springframework.org/schema/beans"
               parseDefaultElement(ele, delegate);
            }
            else {
               // 如果该标签属于其余的名称空间比方:context,aop 等
               //xmlns:aop="http://www.springframework.org/schema/aop"
               //xmlns:context="http://www.springframework.org/schema/context"
               delegate.parseCustomElement(ele);
            }
         }
      }
   }
   else {delegate.parseCustomElement(root);
   }
}

在 Spring 中的 XML 分为两大类,一种是默认的如下:

<bean id="test" class="cn.jack.Test"/>

另一种就是自定义的:

<tx:annotation-driven/>

如果是自定义实现的话,则须要用户实现自定义配置,如果根节点或者节点是应用默认命名的话则应用 parseDefaultElement 进行解析,否则应用 delegate.parseCustomElement 办法对自定义命名空间进行解析。

而判断是默认命名还是自定义命名空间则应用 isDefaultNamespace 办法中的 node.getNamespaceURI()获取命名空间,随后与固定的命名空间 http://www.springframework.org/schema/beans 进行比照,不统一则为自定义命名空间。

对于默认标签解析与自定义标签解析则在下一篇文章中。

正文完
 0