Spring自定義標籤配置的源碼解析與實現

概述

Spring中,從AbstractXmlApplicationContext開始,經過對NamespaceHandler & BeanDefinitionParser,來實現自定義xml配置的功能。php

1 xml加載解析

xml文件的加載,從AbstractXmlApplicationContext.loadBeanDefinitions(DefaultListableBeanFactory)中開始實現:java

@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		// Configure the bean definition reader with this context's
		// resource loading environment.
		beanDefinitionReader.setEnvironment(this.getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// Allow a subclass to provide custom initialization of the reader,
		// then proceed with actually loading the bean definitions.
		initBeanDefinitionReader(beanDefinitionReader);
		// 加載bean定義
		loadBeanDefinitions(beanDefinitionReader);
	}
複製代碼

加載過程是由XmlBeanDefinitionReader實現的,XmlBeanDefinitionReader繼承了AbstractBeanDefinitionReader,過程以下:node

  • 1.AbstractBeanDefinitionReader.loadBeanDefinitions(Resource... resources)
  • 2.XmlBeanDefinitionReader.loadBeanDefinitions(Resource)
  • 3.XmlBeanDefinitionReader.doLoadBeanDefinitions
  • 4.XmlBeanDefinitionReader.registerBeanDefinitions
  • 5.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions
  • 6.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions
  • 7.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions DefaultBeanDefinitionDocumentReader.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)) {
					    // 若是是默認的namespace,則執行parseDefaultElement加載默認的xml元素,如import, alias, bean等;
						parseDefaultElement(ele, delegate);
					}
					else {
					    // 不然執行BeanDefinitionParserDelegate.parseCustomElement(root)加載自定義的xml元素。
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			delegate.parseCustomElement(root);
		}
	}
複製代碼

BeanDefinitionParserDelegate.parseCustomElement中獲取了自定義的namespace,並根據namespace獲取NamespaceHandler,而後執行NamespaceHandler.parse,並返回BeanDefinition.spring

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
                // 獲取namespace
		String namespaceUri = getNamespaceURI(ele);
		// 獲取對應的NamespaceHandler
		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
		if (handler == null) {
			error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
			return null;
		}
		// 返回BeanDefinition
		return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
	}
複製代碼

具體過程以下:bash

  • 1.this.readerContext.getNamespaceHandlerResolver() :ide

      1. 返回 NamespaceHandlerResolver,實現類是DefaultNamespaceHandlerResolver;
      1. DefaultNamespaceHandlerResolver中定義了 String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
  • 2.DefaultNamespaceHandlerResolver.resolve :post

      1. 執行了NamespaceHandler.init
      1. 根據spring.handlers的內容和當前的namespace, 並返回了自定義的NamespaceHandler的實現。

2 NamespaceHander & BeanDefinitionParser

  • org.springframework.beans.factory.xml.BeanDefinitionParser:解析xml並返回BeanDefinition對象。
  • org.springframework.beans.factory.xml.NamespaceHander:將實現的BeanDefinitionParser對象註冊到指定xml元素。

因此,通常直接繼承NamespaceHandlerSupport 或 AbstractSingleBeanDefinitionParser便可。

3 完整實現步驟

3.1 定義一個Bean

public class ServerConfig {
    private String host;
    private int port;
    public String getHost() {
        return host;
    }
    public void setHost(String host) {
        this.host = host;
    }
    public int getPort() {
        return port;
    }
    public void setPort(int port) {
        this.port = port;
    }
}
複製代碼

3.2 建立xsd文件

建立xsd,並放到META-INF下;如文件名爲custom.xsd。ui

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="custom" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans" targetNamespace="custom" >
    <xsd:import namespace="http://www.springframework.org/schema/beans" />
    <xsd:element name="serverConfig">
        <xsd:complexType>
            <xsd:complexContent>
                <xsd:extension base="beans:identifiedType">
                    <xsd:attribute name="host" type="xsd:string" />
                    <xsd:attribute name="port" type="xsd:int" />
                </xsd:extension>
            </xsd:complexContent>
        </xsd:complexType>
    </xsd:element>
</xsd:schema> 
複製代碼

3.3 實現BeanDefinitionParser接口

繼承AbstractSingleBeanDefinitionParser,並重寫getBeanClass和doParse兩個方法,解析custom.xsd中定義的xml節點的屬性。this

public class ServerConfigBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
    @Override
    protected Class<?> getBeanClass(Element element) {
        return ServerConfig.class;
    }
    @Override
    protected void doParse(Element element, BeanDefinitionBuilder builder) {
        String name = element.getAttribute("host");
        String port = element.getAttribute("port");

        if (StringUtils.hasText(name)) {
            builder.addPropertyValue("host", name);
        }
        if (StringUtils.hasText(port)) {
            builder.addPropertyValue("port", Integer.valueOf(port));
        }
    }
}
複製代碼

3.4 實現NameSpaceHander接口

繼承NamespaceHandlerSupport,重寫init()方法:將custom.xsd中定義的xml根節點,註冊爲上面實現的ServerConfigBeanDefinitionParser對象。spa

public class ServerConfigNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        registerBeanDefinitionParser("serverConfig",  new ServerConfigBeanDefinitionParser());
    }
}
複製代碼

3.5 指定xsd文件

classpath下,新建文件 META-INF/spring.schemas,並寫入如下內容:

custom.xsd=classpath:META-INF/custom.xsd
複製代碼

3.6 指定NameSpaceHander的實現類

classpath下,新建文件 META-INF/spring.hander,並寫入如下內容:

custom=xxx.xxx.ServerConfigNamespaceHandler
複製代碼

3.7 spring中引入xsd

在spring的xml配置中,引入上面聲明的xsd:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tinyrpc="custom" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd custom META-INF/custom.xsd" default-lazy-init="false" default-autowire="byName">
       <custom:serverConfig id=」testServer" host=」localhost" port=」8888"></custom:serverConfig>
</beans>
複製代碼

便可聲明一個id爲testServer的Bean。

4 總結

綜上所述,實現基本的自定義xml,按照以下幾個步驟便可:

  • 1.定義Bean;
  • 2.建立xsd;
  • 3.繼承AbstractSingleBeanDefinitionParser並重寫相關方法;
  • 4.繼承NamespaceHandlerSupport並重寫相關方法;
  • 5.經過META-INF/spring.xsd指定xsd文件;
  • 6.經過META-INF/spring.hander指定NamespaceHander;
  • 7 在spring的xml配置中,引入聲明的xsd。

Spring中的aop配置,事務配置等,阿里Dubbo,美團的Pigeon 中自定義的xml配置,均由此方式實現。

瞭解spring的加載過程,可參考Spring加載過程及核心類

相關文章
相關標籤/搜索