Lecture du code source Spring 07 : Le processus de chargement de BeanDefinition (étape de chargement des ressources)

C'est le 24ème jour de ma participation au "Nuggets Daily New Plan · August Update Challenge", cliquez pour voir les détails de l'événement

Basé sur Spring Framework v5.2.6.RELEASE

Suite de l'article précédent : Lecture du code source Spring 06 : Le processus de chargement de BeanDefinition (étape de préparation)

résumer

L'article précédent a présenté le travail effectué dans la phase de préparation du processus de chargement de BeanDefinition, et cet article explore le processus suivant. Une fois la phase de préparation terminée, XmlBeanDefinitionReaderla méthode de la classe est entrée doLoadBeanDefinitionset il y a deux appels de méthode clés :

Document doc = doLoadDocument(inputSource, resource);
int count = registerBeanDefinitions(doc, resource);
复制代码

Comme le montre le nom de la méthode, le premier appel de méthode consiste à charger les ressources de configuration et enfin à obtenir un objet Document, et le second appel de méthode consiste à enregistrer la BeanDefinition dans le conteneur.

Cet article analyse d'abord la part du chargement des ressources.

Analyse des ressources XML

D'abord, allez à la doLoadDocumentméthode:

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
   return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
         getValidationModeForResource(resource), isNamespaceAware());
}
复制代码

documentLoaderLa méthode des variables membres s'appelle ici loadDocument.Afin de faciliter l'analyse ultérieure, voyons de documentLoaderquoi il s'agit. Dans la définition des variables membres on peut trouver :

private DocumentLoader documentLoader = new DefaultDocumentLoader();
复制代码

Ici on est créé directement via le constructeur DefaultDocumentLoader.Dans sa définition de classe, la définition du constructeur n'est pas incluse, on peut donc penser qu'il ne s'agit ici que de créer cet objet. DocumentLoaderComme son nom l'indique, il s'agit d'un chargeur de documents XML .

Ensuite, trouvez DefaultDocumentLoaderla loadDocumentméthode dans :

@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
      ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

   DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
   if (logger.isTraceEnabled()) {
      logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
   }
   DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
   return builder.parse(inputSource);
}
复制代码

Ici, le flux d'entrée de ressource est chargé dans un objet Document. Le processus d'analyse d'un fichier XML dans un objet Document n'est qu'un processus d'analyse de fichier XML, qui dépasse le cadre de cet article et ne sera pas décrit en détail ici.

接下来再来分析loadDocument方法中的几个方法参数,从方法体中的代码看出,这几个参数传入的具体信息会影响 XML 解析的配置,其中,前三个参数的类型都属于org.xml.sax包,并不是 Spring 的一部分。

DocumentLoader#loadDocument方法的参数

inputSource

这里的inpustSource是对资源的输入流inputStream的封装,它其实就代表了要被解析的 XML 的输入流。除了输入流、编码、字符集以外,它还包含了两个成员变量:publicIdsystemId,这两个都是 XML 文档的参数,这里的inpustSource在创建的时候,并没有给这两个成员变量赋值,所以我们先跳过,之后遇到了再做详细介绍。

entityResolver

loadDocument方法中有一个EntityResolver类型的参数,EntityResolver是一个接口,想要知道这里调用方法的时候具体传入了一个什么样的对象,我们需要找到XmlBeanDefinitionReader调用方法时获取参数的 getEntityResolver()方法:

protected EntityResolver getEntityResolver() {
   if (this.entityResolver == null) {
      // Determine default EntityResolver to use.
      ResourceLoader resourceLoader = getResourceLoader();
      if (resourceLoader != null) {
         this.entityResolver = new ResourceEntityResolver(resourceLoader);
      }
      else {
         this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
      }
   }
   return this.entityResolver;
}
复制代码

方法体中获取resourceLoader的方法getResourceLoader()其实就是读取了当前对象的resourceLoader成员变量。在之前的源码阅读(Spring 源码阅读 04:BeanFactory 初始化 )中,我们已经知道,这里的XmlBeanDefinitionReader在创建的时候,就给它设置了resourceLoader的值,因此,以上代码中if语句块会进入第一个条件中,创建一个ResourceEntityResolver类型的 EntityResolver 对象。

继续深入,找到创建ResourceEntityResolver的构造方法:

public ResourceEntityResolver(ResourceLoader resourceLoader) {
   super(resourceLoader.getClassLoader());
   this.resourceLoader = resourceLoader;
}
复制代码

这里调用了父类的构造方法,并且设置了自己的resourceLoader成员变量的值。下面来到它的父类DelegatingEntityResolver中,查看对应的构造方法:

public DelegatingEntityResolver(@Nullable ClassLoader classLoader) {
   this.dtdResolver = new BeansDtdResolver();
   this.schemaResolver = new PluggableSchemaResolver(classLoader);
}
复制代码

从它的名称和构造方法体来看,它是一个代理类型,并且持有了BeansDtdResolver和PluggableSchemaResolver两个类型的EntityResolver。没错,这两个也是EntityResolver的实现类,这几个类的关系是这样的:

说了这么多,这个EntityResolver是用来干什么的呢?

Spring 的 XML 配置文件,对内容和格式都有严格的约束,如果配置文件不符合这些约束要求,就会导致 Spring 初始化失败,因此,在初始化之前,Spring 需要对这些文件进行校验。

XML 的约束文件通常声明在文件中,比如下面的配置文件内容:

跟节点 beans 中这些 URL 就是约束文件的地址。比如其中的spring-beans.xsd,这里的.xsd后缀表示它是一个 XSD 文件,XSD 是 XML Schemas Definition 的简写,也就是用来定义 XML 模式的。

除了 XSD 之外,Spring 还支持 DTD(Document Type Definition,文档类型定义)类型的约束文件。

默认情况下,在需要校验 XML 文件的时候,会根据这个路径,下载对应的约束文件,来对 XML 配置进行校验。这种情况下,并不需要 EntityResolver 的参与。

但是这里有一个问题,如果我们的程序运行在离线环境或者网络受限的环境中,如何对 XML 文件进行校验呢?解决办法就是把这些文件直接继承在 Spring 项目当中。在 Spring 的工程中,可以找到这些文件,比如下面这几个:

那如何让程序找到 Spring 工程中的约束文件,并使用这些文件对相应的 XML 文件进行校验呢?这便是 EntityResolver 的作用,通过实现EntityResolver接口,可以自定义约束文件获取的逻辑。

在上面的代码中可以发现,实际完成这项工作的就是getEntityResolver()方法中创建的DelegatingEntityResolver,它会把具体的任务委托给它持有的两个 EntityResolver 成员变量,分别是BeansDtdResolverPluggableSchemaResolver类型,它们分别负责 DTD 格式的约束文件和 XSD 格式的约束文件的解析。

因此,有了这几个 Spring 定义的EntityResolver之后,就可以让 XML 解析器在获取约束文件的时候,用 Spring 中的离线文件代替需要下载的在线文件。

EntityResolver具体是如何处理约束文件的,不属于本篇所讨论的流程,这里挖个坑,之后单独开一篇来写,写完之后会把链接贴到这里:

errorHandler

接下来在看errorHandler找个参数,从名字就可以看出来,它是一个错误处理器,负责处理 XML 解析过程中出现的异常情况。在 XmlBeanDefinitionReader 中调用this.documentLoader.loadDocument方法的时候,这个参数直接传入了this.errorHandler。我们查看这个成员变量:

private ErrorHandler errorHandler = new SimpleSaxErrorHandler(logger);
复制代码

它是在 XmlBeanDefinitionReader 中直接初始化好的一个 SimpleSaxErrorHandler 类型的对象,并把logger作为参数传递了进去。在查看一下 SimpleSaxErrorHandler 的源码:

public class SimpleSaxErrorHandler implements ErrorHandler {
   private final Log logger;
   /**
* Create a new SimpleSaxErrorHandler for the given
* Commons Logging logger instance.
*/
public SimpleSaxErrorHandler(Log logger) {
      this.logger = logger;
   }
   @Override
   public void warning(SAXParseException ex) throws SAXException {
      logger.warn("Ignored XML validation warning", ex);
   }
   @Override
   public void error(SAXParseException ex) throws SAXException {
      throw ex;
   }
   @Override
   public void fatalError(SAXParseException ex) throws SAXException {
      throw ex;
   }

}
复制代码

其实这里什么都没有处理,只是在需要警告提示的时候写入了一条日志,其他的异常直接抛出了。这里应该也是留给其他的实现类扩展用的。

validationMode

我们之前说到,在 XML 文件被解析的时候,会根据其对应的约束文件,对 XML 进行校验,确保它是符合预设的模式的。Spring 支持 DTD 和 XSD 两种约束文件,两者的约束逻辑是不一样的,那在解析的时候,怎么知道该采样哪种校验方式呢?这就是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;
   }
   // Hmm, we didn't get a clear indication... Let's assume XSD,
   // since apparently no DTD declaration has been found up until
   // detection stopped (before finding the document's root tag).
   return VALIDATION_XSD;
}
复制代码

第一步,先调用getValidationMode()获取默认要使用的模式,这里获取到的是当前类的成员变量validationMode,它的值是VALIDATION_AUTO,因此,第一个if语句块不会被执行。我们接着往下看。

下面又调用了detectValidationMode(resource)方法,通过资源自动探测它的验证模式,如果与VALIDATION_AUTO的值不同,则使用探测到的模式,否则采用 XSD 的验证方式。这里就需要看一下detectValidationMode方法是如何进行探测的。

protected int detectValidationMode(Resource resource) {
   if (resource.isOpen()) {
      throw new BeanDefinitionStoreException(...);
   }

   InputStream inputStream;
   try {
      inputStream = resource.getInputStream();
   }
   catch (IOException ex) {
      throw new BeanDefinitionStoreException(..., ex);
   }

   try {
      return this.validationModeDetector.detectValidationMode(inputStream);
   }
   catch (IOException ex) {
      throw new BeanDefinitionStoreException(..., ex);
   }
}
复制代码

以上代码中核心的就两行,先获取到资源的输入流,然后调用this.validationModeDetector.detectValidationMode方法。这里的validationModeDetector也是直接初始化好的成员变量:

private final XmlValidationModeDetector validationModeDetector = new XmlValidationModeDetector();
复制代码

我们找到它的detectValidationMode方法:

public int detectValidationMode(InputStream inputStream) throws IOException {
   // Peek into the file to look for DOCTYPE.
   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) {
      // Choked on some character encoding...
      // Leave the decision up to the caller.
      return VALIDATION_AUTO;
   }
   finally {
      reader.close();
   }
复制代码

这里的逻辑比较简单,就是判断 XML 文件中是不是包含DOCTYPE,如果包含就采用 DTD 校验,否则采用 XSD 校验。为什么呢?我们分别看一下采用两种约束文件的 XML 文件内容就知道了。

一个采用 XSD 约束的 XML 配置文件是这样的:

<?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 配置 -->
</beans>
复制代码

一个采用 DTD 约束的 XML 配置文件是这样的:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!-- bean 配置 -->
</beans>
复制代码

因此可以通过是不是包含 DOCTYPE 来判断了两者的类型。

namespaceAware

最后来看一下namespaceAware这个参数,它用来设置 XML 解析器对命名空间的支持,这里传入的值是通过 XmlBeanDefinitionReader 的isNamespaceAware()方法获取的,这个方法直接读取了namespaceAware成员变量,它的默认值是false

不过,在之前也给这个成员变量赋过一个值。

Dans l'analyse de code source précédente ( Spring source code reading 04: BeanFactory initialization ), vous pouvez trouver le code pour la création et l'initialisation de XmlBeanDefinitionReader, AbstractXmlApplicationContextil y loadBeanDefinitions(DefaultListableBeanFactory beanFactory)a un tel morceau de code :

XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

initBeanDefinitionReader(beanDefinitionReader);
复制代码

Il s'agit du code dans lequel le beanDefinitionReader est initialement créé, et une méthode initBeanDefinitionReader est appelée sur la dernière ligne du code ci-dessus :

protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
   reader.setValidating(this.validating);
}
复制代码

this.validatingLa valeur ici est trueque XmlBeanDefinitionReaderla validation du fichier XML est activée. Alors, qu'est-ce que cela namespaceAwarea à voir avec cela? Voyons sa setValidatingméthode :

public void setValidating(boolean validating) {
   this.validationMode = (validating ? VALIDATION_AUTO : VALIDATION_NONE);
   this.namespaceAware = !validating;
}
复制代码

Vous pouvez voir que namespaceAwarela valeur est affectée ici en même temps, pour false.

suivre

Cet article analyse le processus de chargement des ressources du fichier de configuration XML en tant qu'objets Document Bien que le processus de chargement spécifique n'appartienne pas à la portée du framework Spring, nous analysons certains contenus liés à la configuration de l'analyseur. L'étape suivante est le processus d'analyse de l'objet Document dans une BeanDefinition, qui est placée dans l'article suivant.

Je suppose que tu aimes

Origine juejin.im/post/7133250145029718029
conseillé
Classement