写点什么

Spring(三)

  • 2022 年 4 月 23 日
  • 本文字数:4767 字

    阅读完需:约 16 分钟


前面已经大体理解了加载 Bean 的那个过程


  • 进行编码

  • 使用 ThreadLocal 来避免循环加载配置文件

  • 获取 Dom 信息并注册


[](()获取 Dom 信息并注册




在 doLoadDocument 方法里面就是获取 Dom 信息


protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {


return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,


getValidationModeForResource(resource), isNamespaceAware());


}


可以看到这个方法,使用了 documentLoader 去实际加载指定的文档


这里面还调用了一个方法 getValidationModeForResource


这个方法的作用是获取对 XML 的验证模式!


[](()获取 XML 的验证模式




XML 的验证模式保证了 XML 文件的正确性,比较常用的验证模式主要有两种,DTD 和 XSD

[](()DTO 和 XSD 验证模式的区别

DTO,称为文档类型定义,是一种 XML 约束模式语言,同时也是 XML 的验证机制,验证的机制是通过比较 XML 文档和 DTD 文件来看文档是否符合规范


XSD,本身就是 XML 语言(Xml Scheme Definition),使用一个指定的 XMl Scheme 来验证某个 XML 文档,来检查该 XML 文档是否符合其要求,即 XML Scheme 可以限制 XML 文档所允许的结构和内容,并根据此检查 XML 文档是否有效


而验证模式的定义使用,是放在配置文件的头描述里面的


比如使用 XSD



使用 DTD 格式的


[](()验证模式的获取

获取验证模式,其实就是 XmlBeanDefinitionReader 调用了 getValidationModeForResource 方法


下面就来看看这个方法的底层

[](()getValidationModeForResource

protected int getValidationModeForResource(Resource resource) {


//获取手动设置的验证模式(XmlBeanDefinitionReader 可以设置 validationMode)


//也就是从代码层面上进行设置


int validationModeToUse = getValidationMode();


//如果验证模式不是未指定的,就返回获取的验证模式


if (validationModeToUse != VALIDATION_AUTO) {


return validationModeToUse;


}


//如果获取的验证模式是未指定,则需要自动的检测(检测配置文件)


int detectedMode = detectValidationMode(resource);


//如果自动检测后得到的验证模式不是未指定的,返回


if (detectedMode != VALIDATION_AUTO) {


return detectedMode;


}


//如果配置文件上依然没有指定验证模式


//默认使用 XSD 模式(注释上写的原因是,因为找不到 DTD 所以使用 XSD)


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


}



从这个方法来看,设置验证模式可以有两种方式


  • 通过 XmlBeanDefinitionReader 来获取 validationMode,即从代码上设置 validationMode,并且优先级最高

  • 通过配置文件去获取 validationMode,优先级最低

  • 如果两个都没有设置,默认使用 XSD 验证模式

[](()配置文件去获取 validationMode(detectValidationMode 方法)

对应的方法就是 detectValidationMode 方法


protected int detectValidationMode(Resource resource) {


//判断配置文件抽象成的 resoure 是不是 open 状态的


//如果处于 open 状态,则不能从该配置文件上读取验证模式


//因为 open 状态,表示已经被打开了


//被打开了就只能被读取一次然后马上关闭,因为可能会造成资源泄露


if (resource.isOpen()) {


throw new BeanDefinitionStoreException(


"Passed-in Resource [" + resource + "] contains an open stream: " +


"cannot determine validation mode automatically. Either pass in a Resource " +


"that is able to create fresh streams, or explicitly specify the validationMode " +


"on your XmlBeanDefinitionReader instance.");


}


//获取文件的 io 流


InputStream inputStream;


try {


inputStream = resource.getInputStream();


}


catch (IOException ex) {


throw new BeanDefinitionStoreException(


"Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +


"Did you attempt to load directly from a SAX InputSource without specifying the " +


"validationMode on your XmlBeanDefinitionReader instance?", ex);


}


//具体的获取验证模式的功能又是交由 validationModeDetector 去执行


try {


return this.validationModeDetector.detectValidationMode(inputStream);


}


catch (IOException ex) {


throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +


resource + "]: an error occurred whilst reading from the InputStream.", ex);


}


}


可见 XmlBeanDefinitionReader 判断了 resource 是否可用之后,调用 validationModeDector 对象去获取校验模式

[](()ValidationModeDector 的 detectValidationMode 方法

在这个方法里面才是真正地读取配置文件去获取校验模式的细节!!!!


public int detectValidationMode(InputStream inputStream) throws IOException {


// Peek into the file to look for DOCTYPE.


// 通过读取配置文件去寻找 DOCTYPE,DOCTYPE 就是配置文件中指定 DTD 校验模式的前缀


// 所以这也体现了一句话,约定大于配置大于编码


// 从这也可以估计到,这里是通过寻找 DTD,找不到 DTD 就使用 XSD。。。


try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {


boolean isDtdValidated = false;


String content;


while ((content = reader.readLine()) != null) {


//这一步其实判断当前行是不是注释


content = consumeCommentTokens(content);


//如果这一行为注释,此时 content 为 null,则读取下一行,这一行不处理


// inComment 代表当前解析位置处于注释里面


if (this.inComment || !StringUtils.hasText(content)) {


continue;


}


//判断当前解析位置是否有 DOCTYPE 如果有 DOCTYPE 就是 DTD 校验模式


if (hasDoctype(content)) {


isDtdValidated = true;


break;


}


if (hasOpeningTag(content)) {


// End of meaningful data...


break;


}


}


// 如果有 DOCTYPE,为 DTD 校验格式,如果没有,则是 XSD 校验格式


return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);


}


catch (CharConversionException ex) {


// Choked on some character encoding...


// Leave the decision up to the caller.


//对于一些不支持的编码,就将主动权返回给调用者去处理。。。。


return VALIDATION_AUTO;


}


}



从源码上可以看出,通过配置文件去获取校验模式,除了不支持编码问题,通过逐行解析文本,如果碰到了 DOCTYPE 字样,就代表是 DTD 模式,如果没有碰到,就代表是 XSD 模式


获取校验模式后,接下来就返回上一层的 XmlBeanDefinitionReader 去解析 Document 了


[](()解析 Document





也就是使用 documentLoader 方法去执行 loadDocument 方法去获取 Document 对象,可以看到,documentLoader 其实是一个接口,而且只有一个实现类 DefaultDocumentLoader,并且里面只有一个方法就是 loadDocument



所以,我们直接进入 DefaultLoadDocument

[](()DefaultLoadDocument

下面就看看那个实现的 loadDocument 方法


@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);


}


其实这个 loadDocument 方法是使用标准的 JAXP 配置的 XML 解析器去简单地加载 XML 文档的,与使用 SAX 解析 XML 套路大致都一样

[](()EntityResolver 对象

返回上一层可以看到,这个对象是调用 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;


}


这个 EntityResolver 是干什么的


EntityResolver 的作用是提供一个如何寻找 DTD、XSD 声明的方法,也就是由程序来实现寻找校验模式的声明


下面来看一下这个接口




这个接口提供了唯一一个方法,这个方法就是定义去哪里寻找校验模式的

[](()解析及注册 BeanDefinitions

经过 DefaultLoadDocument 进行解析后,现在已经为 XML 配置文件生成了一棵 DOM 树了,即 Document 实体,此时就回到了 XmlBeanDefinitionReader 的 doLoadBeanDefinitions 方法



下一步就是进行注册 BeanDefinitions 了,也就是执行 registerBeanDefinitions 方法

[](()registerBeanDefinitions 方法

这个方法就是用来提取以及注册 Bean 的


public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {


//使用 DefaultBeanDefinitionsReader 来实例化 BeanDefinitionDocumentReader 接口


BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();


//记录 BeanDefinitions 的加载个数(即之前加载的个数)


int countBefore = getRegistry().getBeanDefinitionCount();


//加载和注册配置文件的 Bean


documentReader.registerBeanDefinitions(doc, createReaderContext(resource));


//记录本次加载配置文件的 Bean 的个数并返回


return getRegistry().getBeanDefinitionCount() - countBefore;


}


在这个方法中,注册 Bean 交给了 BeanDefinitionDocumentReader 去做,而 XmlBeanDefinitionReader 只关心配置文件进行加载和转化的工作,很好地应用了面向对象的单一职责的原则,将逻辑处理都委托给单一的类进行处理,也就是交由 BeanDefinitionDocumentReader 去做


下面就来看看 BeanDefinitionDocumentReader 是怎么注册 Bean 的

[](()BeanDefinitionDocumentReader 进行注册 Bean

源码如下


@Override


public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {


this.readerContext = readerContext;


//注册的细节还在 doRegisterBeanDefinitions 里面


//可以看到给的参数为 Dom 里面的 DocumentElement,也就是最上层标签,Dom 树的根节点!


doRegisterBeanDefinitions(doc.getDocumentElement());


}

[](()doRegisterBeanDefinitions 方法

源码如下


下面就是真正的注册 Bean 的过程了!


/**


  • Register each bean definition within the given root {@code <beans/>} element.


*/


@SuppressWarnings("deprecation") // for Environment.acceptsProfiles(String...)


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.


//装饰者模式,BeanDefinitionDocumentReader 装饰了 BeanDefinitionParserDelegate


//实际上进行 XML 解析的是 BeanDefinitionParserDelegate 去做的(单一职责)

用户头像

还未添加个人签名 2022.04.13 加入

还未添加个人简介

评论

发布
暂无评论
Spring(三)_Java_爱好编程进阶_InfoQ写作社区