SpringIoC自定義標籤解析

NO IMAGE

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

概述

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

自定義標籤

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

if (delegate.isDefaultNamespace(ele)) {
    this.parseDefaultElement(ele, delegate);
} else {
    delegate.parseCustomElement(ele);
}

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

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 中已經提供了方法供我們直接調用:

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

在代碼調試過程中可以看到此處的數據,如下圖所示:

SpringIoC自定義標籤解析

讀取自定義標籤處理器

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

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, this, this.getNamespaceHandlerResolver());
}

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

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 中實現:

SpringIoC自定義標籤解析

我們在 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 中 。

相關文章

[譯]如何準備你的GitHub,給面試官留個好印象

十幾道含答案的大廠面試題總結

Android源碼分析OkHttp5線程調度

一次Docker容器內大量殭屍進程排查分析