Spring4.3.x 淺析xml配置的解析過程(5)——解析自定義命名空間的標籤

版權聲明:本文爲博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/chyohn/article/details/54945744

概述


在上一篇解析<bean>標籤及其所有子標籤我們詳細探討了如何使用<bean>標籤來創建一個BeanDefintion對象。這一篇我們開始探討一下spring如何處理其它命名空間的xml標籤,比如spring擴展的http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/phttp://www.springframework.org/schema/aop命名空間。

經過前面的探討,我們知道XmlBeanDefinitionReader使用BeanDefinitionDocumentReader對象把Document對象中包含的配置信息轉換成BeanDefinition對象並把它註冊到BeanDefintionRegistry對象中。默認使用DefaultBeanDefinitionDocumentReader來操作Document對象。在DefaultBeanDefinitionDocumentReader的實現中,它的責任是遍歷xml根節點下的子節點,並把處理bean標籤和自定義命名空間的標籤(比如aop:,context:,p:等)的細節委託給BeanDefinitionParserDelegate對象,BeanDefinitionParserDelegate纔是真正解析配置文件的地方。

下面是DefaultBeanDefinitionDocumentReader對象使用BeanDefinitionParserDelegate對象來處理自定義命名空間的標籤的入口,

    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        // 檢查root節點的命名空間是否爲默認命名空間
        // spring配置文件中默認的命名空間爲"http://www.springframework.org/schema/beans"
        if (delegate.isDefaultNamespace(root)) {
            NodeList nl = root.getChildNodes();
            // 遍歷root節點下的所有子節點
            for (int i = 0; i < nl.getLength(); i++) {
                Node node = nl.item(i);
                if (node instanceof Element) {
                    Element ele = (Element) node;
                    // 檢查子節點的命名空間是否爲默認命名空間
                    if (delegate.isDefaultNamespace(ele)) {
                        // 解析默認命名空間的元素節點
                        parseDefaultElement(ele, delegate);
                    } else {
                        // 解析自定義元素節點
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        } else {
            // 解析自定義元素節點
            delegate.parseCustomElement(root);
        }
    }

parseBeanDefinitions方法的主要事情是區分節點標籤是否是默認命名空間的標籤,以及遍歷根節點下的子節點。在這裏對於任何一個節點,如果是默認命名空間的則調用DefaultBeanDefinitionDocumentReader方法parseDefaultElement處理,否則調用BeanDefinitionParserDelegate 的parseCustomElement方法來處理。這篇文章的主題是探討spring如何解析自定義命名空間的標籤,因此我們的入口是parseCustomElement方法,如下是這個方法的源代碼。

    public BeanDefinition parseCustomElement(Element ele) {
        return parseCustomElement(ele, null);
    }

parseCustomElement(Element ele)方法把處理節點的任務傳遞給parseCustomElement(Element ele, BeanDefinition containingBd)方法,這個方法的源碼如下。

    public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
        // 獲取命名空間的uri
        String namespaceUri = getNamespaceURI(ele);
        // 根據NamespaceHandlerResolver對象獲取命名空間的處理器NamespaceHandler對象。
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler == null) {
            error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        }
        // 調用NamespaceHandler的parse方法處理節點
        return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    }

parseCustomElement(Element ele, BeanDefinition containingBd)首先通過節點的命名空間uri字符串獲取命名空間處理器NamespaceHandler對象,然後調用處理器的parse方法處理節點。spring定義了很多命名空間和它們對於的處理器類,因此,我們在這裏需要清楚的是如何獲取NamespaceHandler對象。

獲取命名空間處理器NamespaceHandler對象

下面是spring定義的NamespaceHandler接口的代碼。

public interface NamespaceHandler {

    /**
     * DefaultBeanDefinitionDocumentReader實例化一個NamespaceHandler對象的時候會調用此方法
     */
    void init();

    /**
     * 解析指定的的Element對象,並返回一個BeanDefinition對象。
     */
    BeanDefinition parse(Element element, ParserContext parserContext);

    /**
     * 解析指定的節點,並裝飾指定的BeanDefinitionHolder對象,最後返回一個已經裝飾的BeanDefinitionHolder對象。
     * 這個方法在parse方法之後被調用
     */
    BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext);

}

Spring定義了NamespaceHandlerResolver接口來獲取命名空間處理器,這個接口的定義如下。

public interface NamespaceHandlerResolver {

    /**
     * 解析命名空間的URI字符串並返回一個對應的NamespaceHandler對象,如果沒有找到,則返回null
     */
    NamespaceHandler resolve(String namespaceUri);

}

NamespaceHandlerResolver 接口只定義了一個方法,並且Spring給出了一個默認的實現,那就是DefaultNamespaceHandlerResolver類,我們來看看這個類的resolve(String namespaceUri)方法,源代碼如下。

    @Override
    public NamespaceHandler resolve(String namespaceUri) {
        Map<String, Object> handlerMappings = getHandlerMappings();
        Object handlerOrClassName = handlerMappings.get(namespaceUri);
        if (handlerOrClassName == null) {
            return null;
        } else if (handlerOrClassName instanceof NamespaceHandler) {
            return (NamespaceHandler) handlerOrClassName;
        } else {
            String className = (String) handlerOrClassName;
            try {
                Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
                // 檢查handlerClass 是否實現了NamespaceHandler接口
                if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                    throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
                            "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
                }
                // 實例化NamespaceHandler對象
                NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
                // 初始化NamespaceHandler對象
                namespaceHandler.init();
                // 把NamespaceHandler對象緩存到handlerMappings對象中
                handlerMappings.put(namespaceUri, namespaceHandler);
                return namespaceHandler;
            } catch (ClassNotFoundException ex) {
                throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
                        namespaceUri + "] not found", ex);
            } catch (LinkageError err) {
                throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
                        namespaceUri + "]: problem with handler class file or dependent class", err);
            }
        }
    }

DefaultNamespaceHandlerResolver的resolve方法通過調用getHandlerMappings()方法獲得NamespaceHandler對象與命名空間URI的映射表,並從這個映射表中取到並返回對應的NamespaceHandler對象。resolve方法的重點就在於NamespaceHandler對象映射表的獲取,下面看看getHandlerMappings()方法的源代碼。

    private Map<String, Object> getHandlerMappings() {
        if (this.handlerMappings == null) {
            synchronized (this) {
                if (this.handlerMappings == null) {
                    try {
                        // 這裏的handlerMappingsLocation默認爲"META-INF/spring.handlers"
                        Properties mappings =
                                PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
                        if (logger.isDebugEnabled()) {
                            logger.debug("Loaded NamespaceHandler mappings: " + mappings);
                        }
                        // 創建一個線程安全的Map對象
                        Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
                        // 把Properties對象中的key-value複製到handlerMappings中國
                        CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
                        this.handlerMappings = handlerMappings;
                    } catch (IOException ex) {
                        throw new IllegalStateException(
                                "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
                    }
                }
            }
        }
        return this.handlerMappings;
    }

getHandlerMappings在第一次調用的時候會通過DefaultNamespaceHandlerResolver的handlerMappingsLocation屬性值(默認爲META-INF/spring.handlers)獲取classes路徑和所有jar包中有的相應資源,我們來看看PropertiesLoaderUtils是怎麼使用 loadAllProperties(String resourceName, ClassLoader classLoader)加載資源的,代碼如下。

    public static Properties loadAllProperties(String resourceName, ClassLoader classLoader) throws IOException {
        Assert.notNull(resourceName, "Resource name must not be null");
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            // 獲取默認的ClassLoader對象
            classLoaderToUse = ClassUtils.getDefaultClassLoader();
        }
        // 使用ClassLoader對象來加載class路徑和所有jar包下所匹配的資源
        Enumeration<URL> urls = (classLoaderToUse != null ? classLoaderToUse.getResources(resourceName) :
                ClassLoader.getSystemResources(resourceName));
        Properties props = new Properties();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            URLConnection con = url.openConnection();
            // 爲JNLPCachedJarURLConnection啓動緩存
            ResourceUtils.useCachesIfNecessary(con);

            InputStream is = con.getInputStream();
            try {
                if (resourceName.endsWith(".xml")) {
                    // 加載xml文件
                    props.loadFromXML(is);
                } else {
                    // 加載properties文件
                    props.load(is);
                }
            } finally {
                is.close();
            }
        }
        return props;
    }

loadAllProperties方法從classes路徑下和所有jar包中獲取所有匹配的資源,並把這些資源文件的內容都保存到同一個Properties對象中作爲loadAllProperties方法的返回值。

Spring自定義的NamespaceHandler對象

我們看看Spring都創建了哪些spring.handlers文件及其內容。
a. spring-beans包下的

http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler

b. spring-context包下的

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler

c. spring-aop包下的

http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

d. spring-tx包下的

http\://www.springframework.org/schema/tx=org.springframework.transaction.config.TxNamespaceHandler

e. spring-jdbc包下的

http\://www.springframework.org/schema/jdbc=org.springframework.jdbc.config.JdbcNamespaceHandler

e. spring-webmvc包下的

http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler

spring.handlers文件中=號左邊的是命名空間uri,=號右邊的是命名空間處理器類的全名稱。

我們看看p命名空間的處理器SimplePropertyNamespaceHandler類的源碼,如下。

public class SimplePropertyNamespaceHandler implements NamespaceHandler {

    private static final String REF_SUFFIX = "-ref";


    @Override
    public void init() {
    }

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        parserContext.getReaderContext().error(
                "Class [" + getClass().getName() + "] does not support custom elements.", element);
        return null;
    }

    @Override
    public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
        // p命名空間只處理屬性
        if (node instanceof Attr) {
            Attr attr = (Attr) node;
            String propertyName = parserContext.getDelegate().getLocalName(attr);
            String propertyValue = attr.getValue();
            // 獲取BeanDefinition的MutablePropertyValues對象
            MutablePropertyValues pvs = definition.getBeanDefinition().getPropertyValues();
            if (pvs.contains(propertyName)) {
                parserContext.getReaderContext().error("Property '" + propertyName + "' is already defined using " +
                        "both <property> and inline syntax. Only one approach may be used per property.", attr);
            }
            if (propertyName.endsWith(REF_SUFFIX)) {
                // 獲取屬性名稱
                propertyName = propertyName.substring(0, propertyName.length() - REF_SUFFIX.length());
                // 設置爲bean引用
                pvs.add(Conventions.attributeNameToPropertyName(propertyName), new RuntimeBeanReference(propertyValue));
            } else {
                pvs.add(Conventions.attributeNameToPropertyName(propertyName), propertyValue);
            }
        }
        return definition;
    }

}

再看看aop命名空間的處理器AopNamespaceHandler 類的源碼,如下。

public class AopNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        // 註冊config標籤的的解析器
        registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
        // 註冊aspectj-autoproxy標籤的解析器
        registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
        // 註冊scoped-proxy標籤的裝飾器
        registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());

        // 註冊spring-configured標籤的解析器
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    }

}

上面兩個例子是spring創建NamespaceHandler實現類的兩種方式,第一種是直接實現NamespaceHandler接口提供的方法,這種方式適合於命名空間中標籤只有一個或者解析標籤和屬性的過程很簡單,比如p命名空間;第二種是繼承抽象類NamespaceHandlerSupport 並實現init方法,在init方法中註冊標籤的解析器和裝飾器以及屬性的裝飾器,spring中大多數命名空間處理器都使用這種方式。

NamespaceHandlerSupport向子類提供了三個方法分別用於註冊標籤的解析、標籤的裝飾器、屬性的裝飾器,如下。

    /**
     * 註冊標籤的解析器BeanDefinitionParser對象
     */
    protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
        this.parsers.put(elementName, parser);
    }

    /**
     * 註冊標籤的裝飾器BeanDefinitionDecorator對象
     */
    protected final void registerBeanDefinitionDecorator(String elementName, BeanDefinitionDecorator dec) {
        this.decorators.put(elementName, dec);
    }

    /**
     * 註冊屬性的裝飾器BeanDefinitionDecorator對象
     */
    protected final void registerBeanDefinitionDecoratorForAttribute(String attrName, BeanDefinitionDecorator dec) {
        this.attributeDecorators.put(attrName, dec);
    }

spring爲解析標籤自定義了一個BeanDefinitionParser接口,源碼如下。

public interface BeanDefinitionParser {

    /**
     * 解析指定的節點元素,並返回一個已經註冊到BeanDefinitionRegistry對象(BeanDefinition註冊表)中的BeanDefinition對象。
     */
    BeanDefinition parse(Element element, ParserContext parserContext);

}

spring還爲裝飾標籤和屬性定義了一個BeanDefinitionDecorator接口,源碼如下。

public interface BeanDefinitionDecorator {

    /**
     * 根據指定Node對象裝飾BeanDefinitionHolder對象,這個Node對象可以是屬性,即Attr對象,
     * 也可以是標籤元素,即Element對象
     * 最後返回一個裝飾好了的BeanDefinitionHolder 對象
     */
    BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext);

}

看到這裏,我們再來說說NamespaceHandler和BeanDefinitionParser 以及BeanDefinitionDecorator二者的關係。
a. 對NamespaceHandler來說,它們可有可無。不過對於spring框架本身來說,這樣的設計提高了spring的可維護性、可擴展性和易讀性。
b. 對BeanDefinitionParser 和BeanDefinitionDecorator來說,它們必須受NamespaceHandler的管控和調度,否則它們將一無是處。

總結


(1)spring通過NamespaceHandler對象來解析自定義標籤和屬性,同時也用這個對象來裝飾BeanDefinitionHolder對象。

(2)DefaultNamespaceHandlerResolver用於管理NamespaceHandler實現類。因此可以從DefaultNamespaceHandlerResolver中根據命名空間uri獲取到對應的NamespaceHandler對象。

(3)NamespaceHandler實現類需要在/META-INF/spring.handler文件中註冊,否則不能被DefaultNamespaceHandlerResolver對象加載。

(4)命名空間中有多個標籤需要解析和裝飾,命名空間處理器類最好繼承NamespaceHandlerSupport ,併爲標籤分別定義BeanDefinitionParser解析器或者BeanDefinitionDecorator裝飾器。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章