Spring IoC 自定義標籤解析

前言

本系列所有基於 Spring 5.2.2.BUILD-SNAPSHOT 版本。由於 Spring 整個體系太過於龐大,因此只會進行關鍵部分的源碼解析。html

本篇文章主要介紹 Spring IoC 容器怎麼解析自定義標籤的。java

正文

在分析自定義標籤怎麼解析以前,咱們先看如何自定義一個標籤以及讓其能被 Spring 解析並加載成 beangit

自定義標籤

編寫 XML Schema 文件:定義 XML 結構

首先編寫一個 users.xsd 文件,其中定義了 User 類型的屬性(該類在Spring IoC BeanDefinition 的加載和註冊一文中定義過);內容以下:github

<xsd:schema xmlns="http://ioc.leisurexi.com/schema/users"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://ioc.leisurexi.com/schema/users">

    <xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>

    <!-- 定義 User 類型(複雜類型) -->
    <xsd:complexType name="User">
        <xsd:attribute name="id" type="xsd:long" use="required"/>
        <xsd:attribute name="name" type="xsd:string" use="required"/>
	</xsd:complexType>

    <!-- 定義 user 元素 -->
    <xsd:element name="user" type="User"/>

</xsd:schema>

注意:上面 XML 中的頭部的地址,我是根據本身的包名來修改的,你須要在 resource 目錄下建立對應的路徑並把 xsd 文件放在該目錄下。spring

編寫 user-context.xml 文件,並在其中使用咱們剛纔的自定義標籤;內容以下:數組

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:users="http://ioc.leisurexi.com/schema/users"
       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.xsd
                           http://ioc.leisurexi.com/schema/users
                           http://ioc.leisurexi.com/schema/users.xsd">

    <users:user id="1" name="leisurexi"/>

</beans>

自定義 BeanDefinitionParser 實現:XML 元素與 BeanDefinition 解析

接下來咱們定義一個類來繼承 AbstractSingleBeanDefinitionParser 重寫標籤的解析方法,以下:緩存

public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

    @Override
    protected Class<?> getBeanClass(Element element) {
        return User.class;
    }

    @Override
    protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
        setPropertyValue("id", element, builder);
        setPropertyValue("name", element, builder);
    }

    private void setPropertyValue(String attributeName, Element element, BeanDefinitionBuilder builder) {
        String attributeValue = element.getAttribute(attributeName);
        if (StringUtils.hasText(attributeValue)) {
            builder.addPropertyValue(attributeName, attributeValue);
        }
    }

}

自定義 NamespaceHandler 實現:命名空間綁定

而後咱們實現 NamespaceHandlerSupport 去註冊元素對應的解析類,以下:app

public class UserNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        // 將 "user" 元素註冊對應的 BeanDefinitionParser 實現
        registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
    }

}

實現類寫好了,但還沒完事;還須要在 META-INF 目錄下建立一個 spring.handlers 文件,寫上命名空間和解析類的映射關係,這是 Spring 中的約定,文件名和目錄得同樣;內容以下:less

http\://ioc.leisurexi.com/schema/users=com.leisurexi.ioc.metadata.UserNamespaceHandler

註冊 XML 擴展:命名空間與 XML Schema 映射

META-INF 建立一個 spring.schemas 文件,這也是約定,文件名和目錄得同樣;內容以下:ide

http\://ioc.leisurexi.com/schema/users.xsd=com/leisurexi/ioc/metadata/users.xsd

接着編寫測試類,看是否能夠獲得咱們定義的 bean,代碼以下:

public static void main(String[] args) {
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
    reader.loadBeanDefinitions("META-INF/user-context.xml");
    User user = beanFactory.getBean(User.class);
    System.out.println(user);
}

最後我把目錄結構貼出來:

注意:com.leisurexi.ioc.metadata 是層級目錄,最好目錄一個接一個往下建立。

代碼解析

BeanDefinitionParserDelegate#parseCustomElement

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
    // 獲取自定義標籤的命名空間
    String namespaceUri = getNamespaceURI(ele); 
    if (namespaceUri == null) {
        return null;
    } 
    // 獲取自定義標籤的處理器,見下文詳解
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler == null) {
        error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
        return null;
    } 
    // 進行標籤的解析,見下文詳解
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

上面代碼中的 namespaceUri 就是咱們在 XML 中定義的 http://ioc.leisurexi.com/schema/users

DefaultNamespaceHandlerResolver#resolve

public NamespaceHandler resolve(String namespaceUri) {
    // 命名空間和其處理類的映射
    Map<String, Object> handlerMappings = getHandlerMappings();
    // 根據命名空間獲取處理類的全類名或其實例
    Object handlerOrClassName = handlerMappings.get(namespaceUri);
    if (handlerOrClassName == null) {
        return null;
    }
    // 若是已是NamespaceHandler類型,表明已經實例化過,直接返回
    else if (handlerOrClassName instanceof NamespaceHandler) {
        return (NamespaceHandler) handlerOrClassName;
    }
    // 沒有對handlerOrClassName實例化,進行實例化,並將實例緩存進handlerMappings
    else {
        String className = (String) handlerOrClassName;
        try {
            // 根據全類名獲取對應的Class對象
            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");
            }
            // 實例化對應的Class對象
            NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
            // 執行init()方法,也就是在這裏會註冊元素的BeanDefinitionParser實現
            namespaceHandler.init();
            // 將實例化後的處理類,放入handlerMappings,下次就不用再次建立了
            handlerMappings.put(namespaceUri, namespaceHandler);
            return namespaceHandler;
        }
        // 省略異常處理...
    }
}

上面方法中的 getHandlerMappings() 方法返回的就是 spring.handlers 文件中的內容;key 爲命名空間,value 爲全類名。以下所示:

NamespaceHandlerSupport#parse

public BeanDefinition parse(Element element, ParserContext parserContext) {
    // 獲取BeanDefinition解析器
    BeanDefinitionParser parser = findParserForElement(element, parserContext);
    // 進行標籤的解析,見下文詳解
    return (parser != null ? parser.parse(element, parserContext) : null);
}

private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
    // 獲取標籤元素,好比咱們自定義的 <users:user id="1" name="leisurexi"/>,這裏獲取的就是user
    String localName = parserContext.getDelegate().getLocalName(element);
    // 根據名稱獲取解析器
    BeanDefinitionParser parser = this.parsers.get(localName);
    if (parser == null) {
        parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
    }
    return parser;
}

上面方法中的 parsers 中的內容,就是上面 UserNamespaceHandler 類中重寫 init() 方法中註冊的解析器。

AbstractBeanDefinitionParser#parse

public final BeanDefinition parse(Element element, ParserContext parserContext) {
    // 獲取調用自定義解析方法後的BeanDefinition,見下文詳解
    AbstractBeanDefinition definition = parseInternal(element, parserContext);
    if (definition != null && !parserContext.isNested()) {
        try { 
            // 獲取id屬性,若是有的話,沒有的話就生成一個默認的id
            String id = resolveId(element, definition, parserContext);
            if (!StringUtils.hasText(id)) {
                parserContext.getReaderContext().error("Id is required for element '" + parserContext.getDelegate().getLocalName(element) + "' when used as a top-level tag", element);
            }
            String[] aliases = null;
            if (shouldParseNameAsAliases()) { 
                // 獲取name屬性
                String name = element.getAttribute(NAME_ATTRIBUTE);
                if (StringUtils.hasLength(name)) { 
                    // 若是name屬性不爲空,根據逗號分割成數組做爲別名
                    aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
                }
            } 
            // 用definition、id和aliases構建成BeanDefinitionHolder
            BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
            // 註冊BeanDefinition
            registerBeanDefinition(holder, parserContext.getRegistry()); 
            if (shouldFireEvents()) { 
                // 發送bean註冊完成事件
                BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
                postProcessComponentDefinition(componentDefinition);
                parserContext.registerComponent(componentDefinition); 
            }
        }
        catch (BeanDefinitionStoreException ex) {
            String msg = ex.getMessage();
            parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element);
            return null;
        }
    }
    return definition;
}

上面方法中的 resolveId() 方法會獲取標籤中的 id 屬性,若是沒有的話會調用 BeanDefinitionReaderUtils.generateBeanName() 來生成一個,該方法和 registerBeanDefinition() 方法在Spring IoC 默認標籤解析一文中有介紹。

AbstractSingleBeanDefinitionParser#parseInternal

protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
    // 獲取父bean名稱
    String parentName = getParentName(element); 
    if (parentName != null) {
        builder.getRawBeanDefinition().setParentName(parentName);
    } 
    // 獲取bean的類型,這裏就會調用UserBeanDefinitionParser重寫的getBeanClass()方法
    Class<?> beanClass = getBeanClass(element);
    if (beanClass != null) { 
        // 設置進BeanDefinition中
        builder.getRawBeanDefinition().setBeanClass(beanClass);
    } 
    // 若是沒有重寫getBeanClass()方法,就調用getBeanClassName()方法
    else {
        String beanClassName = getBeanClassName(element);
        if (beanClassName != null) {
            builder.getRawBeanDefinition().setBeanClassName(beanClassName);
        }
    }
    builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
    // 獲取當前BeanDefinition的外層BeanDefinition
    BeanDefinition containingBd = parserContext.getContainingBeanDefinition(); 
    if (containingBd != null) {
        // 內嵌bean必需和外層bean有相同的做用域
        builder.setScope(containingBd.getScope()); 
    }
    if (parserContext.isDefaultLazyInit()) {
        builder.setLazyInit(true);
    }
    // 調用子類重寫的doParse()方法
    doParse(element, parserContext, builder);
    return builder.getBeanDefinition();
}

上面方法中 doParse() 方法就會調用咱們自定義的 UserBeanDefinitionParser 類中的 doParse() 方法。

總結

本文主要介紹了 Spring 對 XML 文件中自定義標籤的解析,咱們能夠從新梳理一下思路:

  1. 根據命名空間獲取對應的 NamespaceHandler
  2. 根據元素名稱獲取對應的 BeanDefinitionParser
  3. 調用重寫的 doParse() 方法來構建 BeanDefinition
  4. 註冊 BeanDefinition 以及發送 bean 註冊完成事件。

最後,我模仿 Spring 寫了一個精簡版,代碼會持續更新。地址:https://github.com/leisurexi/tiny-spring

參考

相關文章
相關標籤/搜索