spring擴展之自定義標簽

jopen 8年前發布 | 48K 次閱讀 Spring JEE框架

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


   

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;
}</pre>這里首先調用parseInternal函數解析出一個BeanDefinition,然后解析兩個通用的屬性id和name,我們看parseInternal函數,它的實現在子類AbstractSingleBeanDefinitionParser中: <br />

   

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="

定義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="

</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()); }</pre>





來自: http://my.oschina.net/nalenwind/blog/599044

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!