Spring IoC自定義標籤解析

概述自定義標籤獲取標籤的命名空間讀取自定義標籤處理器標籤解析css

概述

本文接着 Spring IoC之存儲對象BeanDefinition 一文繼續學習,在學習自定義標籤的知識時,首先咱們先了解一下自定義標籤的實現,歡迎閱讀:Spring自定義標籤的實現java

自定義標籤

parseBeanDefinitions()方法中有這麼一段代碼:node

if (delegate.isDefaultNamespace(ele)) {
    this.parseDefaultElement(ele, delegate);
else {
    delegate.parseCustomElement(ele);
}
複製代碼

若是傳入的標籤不是默認標籤,則調用 parseCustomElement(ele)方法,該方法定義以下:web

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

@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
    // 獲取 namespaceUri
    String namespaceUri = this.getNamespaceURI(ele);
    if (namespaceUri == null) {
        return null;
    } else {
        // 根據 namespaceUri 獲取相應的 Handler
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler == null) {
            this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        } else {
            // 調用自定義的 Handler 處理
            return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
        }
    }
}
複製代碼

處理過程分爲三步:緩存

  1. 獲取標籤的命名空間
  2. 讀取自定義標籤處理器
  3. 標籤解析

獲取標籤的命名空間

標籤的解析是從命名空間的提起開始的,不管是區分 Spring 中默認標籤和自定義標籤 仍是 區分自定義標籤中不一樣標籤的處理器都是以標籤所提供的命名空間爲基礎的,而至於如何提取對應元素的命名空間其實並不須要咱們親內去實現,在 org.w3c.dom.Node 中已經提供了方法供咱們直接調用: app

String namespaceUri = getNamespaceURI(ele);
@Nullable
public String getNamespaceURI(Node node) {
    return node.getNamespaceURI();
}
複製代碼

在代碼調試過程當中能夠看到此處的數據,以下圖所示:dom

讀取自定義標籤處理器

根據 namespaceUri 獲取 Handler,這個映射關係咱們在 Spring.handlers 中已經定義了,因此只須要找到該類,而後初始化返回,最後調用該 Handler 對象的 parse() 方法處理,該方法咱們也提供了實現。因此上面的核心就在於怎麼找到該 Handler 類。調用方法爲ide

this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri)
複製代碼

首先 this.readerContext.getNamespaceHandlerResolver()返回了一個 NamespaceHandlerResolver 對象,該對象在 registerBeanDefinitions 中的 createReaderContext()方法設置的,其中涉及到如下方法:函數

public XmlReaderContext createReaderContext(Resource resource) {
    return new XmlReaderContext(resource, this.problemReporter, this.eventListener, this.sourceExtractor, thisthis.getNamespaceHandlerResolver());
}
複製代碼

XmlReaderContext 構造函數中最後一個參數就是 NamespaceHandlerResolver 對象,該對象由 getNamespaceHandlerResolver() 提供,以下:post

public NamespaceHandlerResolver getNamespaceHandlerResolver() {
    if (this.namespaceHandlerResolver == null) {
        this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
    }
    return this.namespaceHandlerResolver;
}

protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
    ClassLoader cl = (getResourceLoader() != null ? getResourceLoader().getClassLoader() : getBeanClassLoader());
    return new DefaultNamespaceHandlerResolver(cl);
}
複製代碼

因此 getNamespaceHandlerResolver().resolve(namespaceUri) 調用的就是 DefaultNamespaceHandlerResolver 的 resolve()。以下:

public NamespaceHandler resolve(String namespaceUri) {
    // 獲取handlerMapping對象,其鍵爲當前的命名空間url,
    // 值爲當前命名空間的處理邏輯類對象,或者爲處理邏輯類的包含全路徑的類名
    Map<String, Object> handlerMappings = this.getHandlerMappings();
    // 查看是否存在當前url的處理類邏輯,沒有則返回null
    Object handlerOrClassName = handlerMappings.get(namespaceUri);
    if (handlerOrClassName == null) {
        return null;
    } else if (handlerOrClassName instanceof NamespaceHandler) {
        // 若是存在當前url對應的處理類對象,則直接返回該處理對象
        return (NamespaceHandler)handlerOrClassName;
    } else {
        // 若是當前url對應的處理邏輯仍是一個沒初始化的全路徑類名,則經過反射對其進行初始化
        String className = (String)handlerOrClassName;

        try {
            Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
            if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri + "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
            } else {
                // 初始化類
                NamespaceHandler namespaceHandler = (NamespaceHandler)BeanUtils.instantiateClass(handlerClass);
                // 調用 init() 方法
                namespaceHandler.init();
                // 記錄在緩存
                handlerMappings.put(namespaceUri, namespaceHandler);
                return namespaceHandler;
            }
        } catch (ClassNotFoundException var7) {
            throw new FatalBeanException("Could not find NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]", var7);
        } catch (LinkageError var8) {
            throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]", var8);
        }
    }
}
複製代碼

首先調用 getHandlerMappings() 獲取全部配置文件中的映射關係 handlerMappings ,該關係爲 <命名空間,類路徑>,而後根據命名空間 namespaceUri 從映射關係中獲取相應的信息,若是爲空或者已經初始化了就直接返回,不然根據反射對其進行初始化,同時調用其 init() 方法,最後將該 Handler 對象緩存。 init() 方法主要是將自定義標籤解析器進行註冊,如我測試代碼中的 init()

public class MyNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
//        registerBeanDefinitionParser("car",new CarBeanDefinitionParser());
        registerBeanDefinitionParser("xxx",new CarParser(Car.class));
    }
}
複製代碼

當獲得自定義命名空間處理後會立刻執行 namespaceHandler.init() 來進行自定義 BeanDefinitionParser的註冊,在這裏,你能夠註冊多個標籤解析器。init()中的 registerBeanDefinitionParser 方法 其實就是將映射關係放在一個 Map 結構的 parsers 對象中:private final Map parsers

標籤解析

獲得瞭解析器和分析的元素後,Spring 就能夠將解析工做委託給自定義解析器去解析了,對於標籤的解析使用的是:NamespaceHandler.parse(ele, new ParserContext(this.readerContext, this, containingBd))方法,進入到方法體內:

public BeanDefinition parse(Element element, ParserContext parserContext) {
    BeanDefinitionParser parser = this.findParserForElement(element, parserContext);
    return parser != null ? parser.parse(element, parserContext) : null;
}
複製代碼

調用 findParserForElement() 方法獲取 BeanDefinitionParser 實例,其實就是獲取在 init() 方法裏面註冊的實例對象。以下:

private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
    //獲取元素名稱,也就是<myTag:xxx中的 xxx
    String localName = parserContext.getDelegate().getLocalName(element);
    //根據 user 找到對應的解析器,也就是在
    //registerBeanDefinitionParser("xxx",new CarParser(Car.class));
    //中註冊的解析器
    BeanDefinitionParser parser = this.parsers.get(localName);
    if (parser == null) {
        parserContext.getReaderContext().fatal(
                "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
    }
    return parser;
}
複製代碼

獲取 localName,在上面的例子中就是 : xxx,而後從 Map 實例 parsers 中獲取 CarParser 實例對象。返回 BeanDefinitionParser 對象後,調用其 parse(),該方法在 CarParser 中實現:

咱們在 CarParser 類中定義了兩個方法以下:

public class CarParser implements BeanDefinitionParser {
    private Class<?> beanclass;

    public CarParser(Class<?> beanclass) {
        this.beanclass = beanclass;
    }

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClass(beanclass);
        beanDefinition.setLazyInit(false);

        String brand = element.getAttribute("brand");
        String color = element.getAttribute("color");
        double price = Double.valueOf(element.getAttribute("price"));
        int maxSpeed = Integer.valueOf(element.getAttribute("speed"));

        beanDefinition.getPropertyValues().add("brand",brand);
        beanDefinition.getPropertyValues().add("color",color);
        beanDefinition.getPropertyValues().add("price",price);
        beanDefinition.getPropertyValues().add("maxSpeed",maxSpeed);
        BeanDefinitionRegistry beanDefinitionRegistry = parserContext.getRegistry();
//        beanDefinitionRegistry.registerBeanDefinition(beanclass.getName(),beanDefinition);//註冊bean到BeanDefinitionRegistry中

        String id = element.getAttribute("id");
        beanDefinitionRegistry.registerBeanDefinition(id,beanDefinition);
        return beanDefinition;
    }

}
複製代碼

自定義的 parse()方法首先定義建立一個 RootBeanDefinition,setBeanClass()方法至關於標籤中的 class 屬性,經過 element 獲取到各個屬性填寫的值,而後填充到 beanDefinition 的 propertyValues 屬性中,用於以後 bean 實例的建立。id 屬性仍是用來標識標籤,最後將 id 和 beanDefinition 一塊兒註冊到BeanDefinitionRegistry 中。

至此,自定義標籤的解析過程已經分析完成了。其實整個過程仍是較爲簡單:首先會加載 handlers 文件,將其中內容進行一個解析,造成 這樣的一個映射,而後根據獲取的 namespaceUri 就能夠獲得相應的類路徑,對其進行初始化等到相應的 Handler 對象,調用 parse() 方法,在該方法中根據標籤的 localName 獲得相應的 BeanDefinitionParser 實例對象,調用 parse() ,該方法定義在 BeanDefinitionParser 的實現類中。對於自定義的 Parser 類,首先須要與 bean 類相關聯,這裏咱們採用的是設置個 beanclass 屬性, 最爲重要的是 doParse()方法,該方法將標籤中填寫的屬性值,填充到一個新的 BeanDefinition 中,最後將其註冊到 BeanDefinitionRegistry 中 。

相關文章
相關標籤/搜索