spring容器实现之获取Document实例终结篇

在上篇spring容器实现之获取Document实例上中DefaultDocumentLoader#loadDocument()方法中,遗留了一个问题,参数EntityResolver,何为EntityResolver?官方给出了一个这样的解释:

如果SAX应用程序需要实现自定义处理外部实体,则必须实现此接口并使用setEntityResolver方法想SAX驱动器注册一个实例,详细点说,就是对于解析一个XML,SAX首先读取该xml的文档的声明,在然后根据申明去寻找相应的DTD定义.
其次EntityResolver本身就提供了一个如何寻找DTD的方法,实质上是由程序去寻找.该扯的也扯了,接下来看正题:

 public abstract InputSource resolveEntity (String publicId,
                                           String systemId)
    throws SAXException, IOException;

上述方法是来自EntityResolver接口的方法,可以看到该方法只接受两个参数
publicId:是一个引用外部实体的公共的标识,允许可以为null
systemId:引用外部实体的系统标识.
最后我们可以发现该方法返回一个inputSource对象.接下来看一下它的实现类:

最终我们来到了xmlBeanDefinitionReader#getEntityResolver,代码如下:

/**返回一个解析器,如果没有指定则构建一个*/
protected EntityResolver getEntityResolver() {
    //当前没有指定解析器
    if (this.entityResolver == null) {
        //确认使用默认的
        ResourceLoader resourceLoader = getResourceLoader();
        if (resourceLoader != null) {
            this.entityResolver = new ResourceEntityResolver(resourceLoader);
        }
        else {
            this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
        }
    }
    return this.entityResolver;
}

上面方法就是获取一个EntityResolver解析器,简单的说一下:

  • 首先判断是否有指定的解析器供使用,显然在代码中是没有的.
  • 没有指定的解析器使用,需要使用默认的,首先构建一个ResourceLoader加载器
  • 构建的加载器如果不为null的情况下:
  • 给ResourceEntityResolver设置属性(ResourceLoader).
    -为null的情况下:
  • 首先获取一个资源加载器,然后设置DelegatingEntityResolver.
  • 最后返回默认的解析器
ResourceLoader的构建过程

跟踪代码来到AbstractBeanDefinitionReader类中:

 private ResourceLoader resourceLoader;

public void setResourceLoader(@Nullable ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
}

public ResourceLoader getResourceLoader() {
    return this.resourceLoader;
}

代码简单,无需多解释,接着看如果ResourceLoader不为null的情况下:

跟踪代码我们来到DelegatingEntityResolver(这是一个代理解析器),看代码:

''''''''
/** Suffix for DTD files. */
public static final String DTD_SUFFIX = ".dtd";

/** Suffix for schema definition files. */
public static final String XSD_SUFFIX = ".xsd";

//文档类型的解析器
private final EntityResolver dtdResolver;
//schema类型的解析器
private final EntityResolver schemaResolver;
  /***
 * 通过类加载器构建不同的解析器
 * @param classLoader
 */
public DelegatingEntityResolver(@Nullable ClassLoader classLoader) {
    //构建加载DTD文档的加载器
    this.dtdResolver = new BeansDtdResolver();
    //构建XSD类型的解析器
    this.schemaResolver = new PluggableSchemaResolver(classLoader);
}

上述代码主要是通过DelegatingEntityResolver去实现解析器的获取,具体是那种得看spring是如何调配的了,上面代码分别构建了不同类型的解析器,分别来看:

DTD解析器

我们发现spring是通过BeansDtdResolver()来构建的跟踪代码进去:

''''''
  public class BeansDtdResolver implements EntityResolver {
//文档末尾以.dtd
private static final String DTD_EXTENSION = ".dtd";
//文档的开头是spring-beans的
private static final String DTD_NAME = "spring-beans";

private static final Log logger = LogFactory.getLog(BeansDtdResolver.class);


@Override
@Nullable
//解析过程
public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId) throws IOException {
    if (logger.isTraceEnabled()) {
        logger.trace("Trying to resolve XML entity with public ID [" + publicId +
                "] and system ID [" + systemId + "]");
    }
    //文档的标识以.dtd结尾
    if (systemId != null && systemId.endsWith(DTD_EXTENSION)) {
        //从后往前找最后一个以/开头的元素
        int lastPathSeparator = systemId.lastIndexOf('/');
        //从前往后找获取以上面结尾且name为DTD_NAME的元素
        int dtdNameStart = systemId.indexOf(DTD_NAME, lastPathSeparator);
        if (dtdNameStart != -1) {
            //拼接
            String dtdFile = DTD_NAME + DTD_EXTENSION;
            if (logger.isTraceEnabled()) {
                logger.trace("Trying to locate [" + dtdFile + "] in Spring jar on classpath");
            }
            try {
                //封装
                Resource resource = new ClassPathResource(dtdFile, getClass());
                InputSource source = new InputSource(resource.getInputStream());
                //设置相应publicId和systemId属性
                source.setPublicId(publicId);
                source.setSystemId(systemId);
                if (logger.isTraceEnabled()) {
                    logger.trace("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
                }
                return source;
            }
            catch (FileNotFoundException ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in classpath", ex);
                }
            }
        }
    }

    // Fall back to the parser's default behavior.
    return null;
}

从代码中发现,原来DTD加载的解析器交给了BeansDtdResolver#resolveEntity方法来处理,解析过程很明确,可以发现的是在解析的过程中,是直接通过systemId直接去截取最后的.dtd结尾然后去当前路径下寻找,这里涉及到了systemId和publicId的构造,简单的来看一下各自的定义:

DTD的模式
publicId:-//SPRING//DTD BEAN 2.0//EN
systemId:[http://www.springframework.org/dtd/spring-beans.dtd](http://www.springframework.org/dtd/spring-beans.dtd)模式模式
XSD模式
publicId:null
systemId:[http://www.springframework.org/schema/beans/spring-beans.xsd](http://www.springframework.org/schema/beans/spring-beans.xsd)

XSD解析器

关于XSD的解析器是如何实现的直接看代码:

/**默认的XSD映射路径*/
public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";
@Nullable
private final ClassLoader classLoader;
private final String schemaMappingsLocation;
@Nullable
/***
 * 保存对应的映射
 */
private volatile Map<String, String> schemaMappings;
public PluggableSchemaResolver(@Nullable ClassLoader classLoader) {
    this.classLoader = classLoader;
    this.schemaMappingsLocation = DEFAULT_SCHEMA_MAPPINGS_LOCATION;
}

该PluggableSchemaResolver类中还有几个方法,想看的可以自己看看源码这里就不在解释了.......

转载于:https://www.jianshu.com/p/ede95b41478f

猜你喜欢

转载自blog.csdn.net/weixin_34023863/article/details/91252277