spring源碼閱讀筆記(二)——自定義標籤 原

spring擴展之自定義標籤

    不知大家在看到那些大牛們在spring裏寫各種擴展工具,各種方便有沒有很羨慕呢?接下來我給大家介紹一下如何通過自定義標籤的形式來擴展spring.
    要通過自定義標籤來擴展spring,首先我們應該知道spring是如何解析標籤,並將其相關信息存儲在內部數據結構中的,這樣我們才能知道要實現或繼承覆寫那些接口或抽象類的函數。

    spring在解析xml的過程中會執行到DefaultBeanDifinitionDocumentReader類的parseBeanDefinitions函數代碼如下:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();
        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);
    }
}
其中的parseDefaultElement函數是用來解析spring文檔中的默認標籤,像beans,import等等,而parseCustomElement函數就是用來解析我們自定義標籤的入口了。代碼如下:
public BeanDefinition parseCustomElement(Element ele) {
    return parseCustomElement(ele, null);
}
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
    String namespaceUri = getNamespaceURI(ele);
    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));
}
這裏首先獲取xml元素的命名空間,然後根據命名空間調哦嗯resolve函數來獲取一個NamespaceHandler,重點在這個resolve函數上。點進去:(該函數所屬DefaultNamespaceHandlerResolver類)
   
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);
            if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
                        "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
            }
            NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
            namespaceHandler.init();
            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);
        }
    }
}

可以看到它首先獲取一個handlerMappings,然後剩下的工作就是去根據這個命名空間去獲取或使用反射去實例化一個NamespaceHandler,
實例話的時候會調用其init函數來初始化NamespaceHandler,這個函數中可以做一些註冊標籤解析器的動作,這個後續會詳細說明。下面不用我說,你肯定早就點進 getHandlerMappings函數看其實現了吧,這裏面它根據handlerMappingsLocation指定的位置,默認就是打好的jar包裏的META-INF/spring.handlers文件,PropertiesLoaderUtils.loadAllProperties函數會把所有jar包下的META-INF/spring.handlers文件全部讀取一遍,將文件中的類似於
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
這個結構的鍵值對存於properties中(這一鍵值對中指定了命名空間和命名空間處理器的對應關係),轉成map從getHandlerMappings返回。所以我們需要編寫我們自己的NamespaceHandler類和指定映射關係的spring.handlers文件。

當然要自定義標籤,還要寫一個定義標籤的xsd定義文件和將命名空間指定到你定義的xsd文件映射關係的文件spring.schemas文件,
spring.schemas文件和spring.handlers放到同一目錄下。schemas文件內容示例如下:
http\://www.xxxx.com/schema/qmq/qmq-2.0.0.xsd=META-INF/qmq-2.0.0.xsd
http\://www.xxxx.com/schema/qmq/qmq.xsd=META-INF/qmq-2.0.0.xsd
spring中已經給我們提供了一個NamespaceHandlerSupport抽象類,他裏面提供了一個存儲標籤名稱到BeanDefinitionParser標籤解析器的映射關係的map,調用registerBeanDefinitionParser函數可以註冊BeanDefinitionParser到map中去解析相應的標籤。BeanDefinitionParser定義如下:
public interface BeanDefinitionParser {
    BeanDefinition parse(Element element, ParserContext content);
}
這樣在上文的parseCustomElement中調用NamespaceHandler的parse函數的時候就會根據標籤名稱調用我們註冊的解析器的parse函數代碼如下:(NamespaceHandlerSupport)
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
        this.parsers.put(elementName, parser);
    }


    public BeanDefinition parse(Element element, ParserContext parserContext) {
        return findParserForElement(element, parserContext).parse(element, parserContext);
    }


    private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
        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;
    }
剩下的工作就是編寫BeanDefinitionParser,spring中爲我們提供了一個抽象類AbstractSingleBeanDefinitionParser來方便我們擴展,上面說道BeanDefinitionParser的入口函數是parse,這個函數的實現在AbstractBeanDefinitionParser類中,代碼如下:

   
public final BeanDefinition parse(Element element, ParserContext parserContext) {
        AbstractBeanDefinition definition = parseInternal(element, parserContext);
        if (definition != null && !parserContext.isNested()) {
            try {
                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 = new String[0];
                String name = element.getAttribute(NAME_ATTRIBUTE);
                if (StringUtils.hasLength(name)) {
                    aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
                }
                BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
                registerBeanDefinition(holder, parserContext.getRegistry());
                if (shouldFireEvents()) {
                    BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
                    postProcessComponentDefinition(componentDefinition);
                    parserContext.registerComponent(componentDefinition);
                }
            }
            catch (BeanDefinitionStoreException ex) {
                parserContext.getReaderContext().error(ex.getMessage(), element);
                return null;
            }
        }
        return definition;
    }
這裏首先調用parseInternal函數解析出一個BeanDefinition,然後解析兩個通用的屬性id和name,我們看parseInternal函數,它的實現在子類AbstractSingleBeanDefinitionParser中:
   
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
        String parentName = getParentName(element);
        if (parentName != null) {
            builder.getRawBeanDefinition().setParentName(parentName);
        }
        Class<?> beanClass = getBeanClass(element);
        if (beanClass != null) {
            builder.getRawBeanDefinition().setBeanClass(beanClass);
        }
        else {
            String beanClassName = getBeanClassName(element);
            if (beanClassName != null) {
                builder.getRawBeanDefinition().setBeanClassName(beanClassName);
            }
        }
        builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
        if (parserContext.isNested()) {
            // Inner bean definition must receive same scope as containing bean.
            builder.setScope(parserContext.getContainingBeanDefinition().getScope());
        }
        if (parserContext.isDefaultLazyInit()) {
            // Default-lazy-init applies to custom bean definitions as well.
            builder.setLazyInit(true);
        }
        doParse(element, parserContext, builder);
        return builder.getBeanDefinition();
    }
這裏先獲取bean的parentname,然後獲取bean的class,我們看到他先調用的是getBeanClass函數,如果反回空纔會調用getBeanClassName,所以我們覆寫AbstractSingleBeanDefinitionParser類的時候只要實現這兩個中的一個函數就可以了,通常是getBeanClass。從上面的函數中我們還可以看到最後的解析全部委託給了doParse函數,我們解析自己的自定義標籤就在這個函數中實現。如果需要更改bean的表示id,還可以覆寫resolveId函數。

好了,道理講完了,上代碼,代碼示例取自《spring源碼深度剖析》69頁起:

定義bean用來接收配置:
public class User {
    private String userName;
    private String email;
    //省略set,get方法
}

定義xsd文件user.xsd:
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.excample.com/schema/user"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.excample.com/schema/user"
        elementFormDefault="qualified">
    <xsd:element name="user">
        <xsd:complexType>
            <xsd:attribute name="id" type="xsd:string"/>
            <xsd:attribute name="userName" type="xsd:string"/>
            <xsd:attribute name="email" type="xsd:string"/>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>


定義BeanDefinitionParser:
public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
    @Override
    protected Class getBeanClass(Element element) {
        return User.class;
    }


    @Override
    protected void doParse(Element element, BeanDefinitionBuilder builder) {
        String userName = element.getAttribute("userName");
        String email = element.getAttribute("email");
        builder.addPropertyValue("userName", userName);
        builder.addPropertyValue("email", email);
    }
}


定義NamespaceHandler:
public class MyNamespaceHandler extends NamespaceHandlerSupport {
    public void init() {
        registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
    }
}


spring.handlers:
http\://http://www.example.com/schema/user=MyNamespaceHandler


spring.schemas:
http\://http://www.example.com/schema/user.xsd=META-INF/user.xsd


測試:
spring.xml配置文件:
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:user="http://www.example.com/schema/user"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.example.com/schema/user http://www.example.com/schema/user.xsd">


    <user:user id="user" userName="test" email="test"/>
</beans>


public static void main(String[] s) {
    ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
    User user = (User)context.getBean("user");
    System.out.print(user.getUesrName() + user.getEmail());
}





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