spring加載xml驗證文件(dtd,xsd)分析

入口

正如我們所知,spring容器啟動會經歷如下幾個步驟:
1、定位,定位到資源文件,并且解析為Resource對象
2、加載,加載xml,將xml文件解析為對應的BeanDefinition
3、注冊,注冊對應的BeanDefinition
下面以AbstractXmlApplicationContext.loadBeanDefinitions為入口進行分析:

    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // Create a new XmlBeanDefinitionReader for the given BeanFactory.
        //為當前工廠創建xml解析器
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        // Configure the bean definition reader with this context's
        // resource loading environment.
        //通過上下文的資源加載環境配置bean解析器
        beanDefinitionReader.setEnvironment(this.getEnvironment());//配置當前環境
        beanDefinitionReader.setResourceLoader(this);//配置資源解析器
        //配置schemas或者dtd的資源解析器,EntityResolver維護了url->schemalocation的路徑
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

        // Allow a subclass to provide custom initialization of the reader,
        // then proceed with actually loading the bean definitions.
        //子類提供自定義的reader的初始化方法
        initBeanDefinitionReader(beanDefinitionReader);
        //加載bean定義
        loadBeanDefinitions(beanDefinitionReader);
    }

定位到beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
ResouceEntityResolver,該類往上追述可知父類為EntityResolver。

何為EntityResolver

如果SAX應用程序需要實現自定義處理外部實體,則必須實現此接口,并使用setEntityResolver方法向SAX 驅動器注冊一個實例.也就是說,對于解析一個xml,sax首先會讀取該xml文檔上的聲明,根據聲明去尋找相應的dtd定義,以便對文檔的進行驗證,默認的尋找規則,(即:通過網絡,實現上就是聲明DTD的地址URI地址來下載DTD聲明),并進行認證,下載的過程是一個漫長的過程,而且當網絡不可用時,這里會報錯,就是應為相應的dtd沒找到,

EntityResolver 的作用

就是項目本身就可以提供一個如何尋找DTD/XSD的聲明方法
即:由程序來實現尋找DTD/XSD聲明的過程,比如我們將DTD/XSD放在項目的某處在實現時直接將此文檔讀取并返回個SAX即可,這樣就避免了通過網絡來尋找DTD/XSD的聲明
查看EntityResolver的接口

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

}

傳入的參數為publicId以及systemId:

對于DTD解析的配置文件:

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

publicId=-//SPRING//DTD BEAN//EN
systemId=http://www.springframework.org/dtd/spring-beans.dtd

對于xsd解析的配置文件:

<?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-2.5.xsd">
...
</beans> 

publicId=null
systemId=http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

spring獲取xml驗證文件的過程

spring在獲取驗證文件(dtd或者xsd)的時候,先讀取xml的聲明,判斷采用dtd還是xsd的方式進行讀取:
追述ResourceEntityResolver的父類,可得父類為DelegatingEntityResolver,該類維護了兩個屬性:

    private final EntityResolver dtdResolver;//dtd解析器

    private final EntityResolver schemaResolver;//xsd解析器
    
        public DelegatingEntityResolver(@Nullable ClassLoader classLoader) {
        this.dtdResolver = new BeansDtdResolver();//DTD解析器
        this.schemaResolver = new PluggableSchemaResolver(classLoader);//schema解析器
    }
    //讀取xml聲明,如果有.dtd后綴,調用dtd解析器;如果.xsd后綴,調用schema解析器
    public InputSource resolveEntity(String publicId, @Nullable String systemId) throws SAXException, IOException {
        if (systemId != null) {
            if (systemId.endsWith(DTD_SUFFIX)) {
                return this.dtdResolver.resolveEntity(publicId, systemId);
            }
            else if (systemId.endsWith(XSD_SUFFIX)) {
                return this.schemaResolver.resolveEntity(publicId, systemId);
            }
        }
        return null;
    }

spring根據xml聲明,將解析的任務委托給dtd解析器BeansDtdResolver或者PluggableSchemaResolver;

BeansDtdResolver解析

1538990352061.png

它會尋找當前classpath路徑下spring-beans.dtd文件:/org/springframework/beans/factory/xml/spring-beans.dtd


1538990352066.png

PluggableSchemaResolver解析

//讀取路徑,META-INF/spring.schemas
public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";
//將schemas路徑以URL->filepath的形式存儲
private volatile Map<String, String> schemaMappings;

首先,PluggableSchemaResolver會加載META-INF/spring.schemas下所有schemas的信息存儲在schemaMappings

    private Map<String, String> getSchemaMappings() {
        Map<String, String> schemaMappings = this.schemaMappings;
        //雙重鎖檢查
        if (schemaMappings == null) {
            synchronized (this) {
                schemaMappings = this.schemaMappings;
                if (schemaMappings == null) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
                    }
                    try {
                        //加載路徑為META-INF/spring.schemas,以Properties形式存儲
                        Properties mappings =
                                PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
                        if (logger.isDebugEnabled()) {
                            logger.debug("Loaded schema mappings: " + mappings);
                        }
                        //將加載的內容轉成map
                        Map<String, String> mappingsToUse = new ConcurrentHashMap<>(mappings.size());
                        CollectionUtils.mergePropertiesIntoMap(mappings, mappingsToUse);
                        schemaMappings = mappingsToUse;
                        //使用schemaMappings存儲
                        this.schemaMappings = schemaMappings;
                    }
                    catch (IOException ex) {
                        throw new IllegalStateException(
                                "Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex);
                    }
                }
            }
        }
        return schemaMappings;
    }

如何解析對應xsd文件

    public InputSource resolveEntity(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 + "]");
        }

        if (systemId != null) {
            //根據URL即systemid,找到本地xsd文件的存儲路徑
            String resourceLocation = getSchemaMappings().get(systemId);
            if (resourceLocation != null) {
                //將對應xsd文件轉為Resource
                Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
                try {
                    InputSource source = new InputSource(resource.getInputStream());
                    source.setPublicId(publicId);
                    source.setSystemId(systemId);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
                    }
                    return source;
                }
                catch (FileNotFoundException ex) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Couldn't find XML schema [" + systemId + "]: " + resource, ex);
                    }
                }
            }
        }
        return null;
    }

因此,此類一共做了這么幾件事:
1、將spring-schemas的信息以URL->schemalocation的形式存儲在map中,如圖


1538991356375.png

1538991475107.png

2、通過xml聲明的systemid,可以獲取到對應xsd文件在當前路徑下的地址,然后進行加載

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,881評論 18 139
  • 對于java中的思考的方向,1必須要看前端的頁面,對于前端的頁面基本的邏輯,如果能理解最好,不理解也要知道幾點。 ...
    神尤魯道夫閱讀 829評論 0 0
  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,941評論 6 342
  • 我有一個朋友,Zoe,長得特別好看,每次見到她,都忍不住多看幾眼,五官精致,身材氣質好。還特別幽默,會說話,跟她聊...
    Joyce姐姐閱讀 343評論 0 2
  • 戊戌春月,碧山諸子采風於三國城,余因故未成,然少慕功業,竊引古人同志,雖非正説,暢想私懷,往來彥俊...
    雙桐軒主人閱讀 343評論 0 0