Spring自定义标签配置的源码解析与实现

1,660 阅读2分钟

概述

Spring中,从AbstractXmlApplicationContext开始,通过对NamespaceHandler & BeanDefinitionParser,来实现自定义xml配置的功能。

1 xml加载解析

xml文件的加载,从AbstractXmlApplicationContext.loadBeanDefinitions(DefaultListableBeanFactory)中开始实现:

        @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。

Spring环境的加载,需要调用AbstractApplicationContext.refresh(),从refresh()开始,完整过程如下:

    1. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    1. AbstractRefreshableApplicationContext.refreshBeanFactory();
    1. AbstractXmlApplicationContext.loadBeanDefinitions(DefaultListableBeanFactory);
    1. AbstractBeanDefinitionReader.loadBeanDefinitions(Resource... resources);
    1. XmlBeanDefinitionReader.loadBeanDefinitions(Resource);
    1. XmlBeanDefinitionReader.doLoadBeanDefinitions;
    1. XmlBeanDefinitionReader.registerBeanDefinitions;
    1. DefaultBeanDefinitionDocumentReader.registerBeanDefinitions;
    1. DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions;
    1. 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.

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));
	}

具体过程如下:

  • 1.this.readerContext.getNamespaceHandlerResolver() :

      1. 返回 NamespaceHandlerResolver,实现类是DefaultNamespaceHandlerResolver;
      1. DefaultNamespaceHandlerResolver中定义了 String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
  • 2.DefaultNamespaceHandlerResolver.resolve :

      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。

<?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节点的属性。

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对象。

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.schemas指定xsd文件;
  • 6.通过META-INF/spring.hander指定NamespaceHander;
  • 7 在spring的xml配置中,引入声明的xsd。

Spring中的aop配置,事务配置等,阿里Dubbo,美团的Pigeon 中自定义的xml配置,均由此方式实现。

了解spring的加载过程,可参考Spring加载过程及核心类