Spring源碼-IOC容器(八)-NamespaceHandler與自定義xml

Spring IOC容器 源碼解析系列,建議你們按順序閱讀,歡迎討論java

(spring源碼均爲4.1.6.RELEASE版本)node

  1. Spring源碼-IOC容器(一)-構建簡單IOC容器
  2. Spring源碼-IOC容器(二)-Bean的定位解析註冊
  3. Spring源碼-IOC容器(三)-GetBean
  4. Spring源碼-IOC容器(四)-FactoryBean
  5. Spring源碼-IOC容器(五)-Bean的初始化
  6. Spring源碼-IOC容器(六)-bean的循環依賴
  7. Spring源碼-IOC容器(七)-ApplicationContext
  8. Spring源碼-IOC容器(八)-NamespaceHandler與自定義xml
  9. Spring源碼-IOC容器(九)-Component-Scan源碼解析
  10. Spring源碼-IOC容器(十)-@Autowired解析

前言

在spring的xml配置文件中,當咱們想使用spring-context或者spring-aop的配置時,都是有前綴的標籤。如:spring

<context:component-scan/>
<aop:config/>

而普通的<bean>標籤則是沒有前綴的,爲何會有這樣的區別呢?網絡

由於spring的xml文件默認的名稱空間(namespace)是http://www.springframework.org/schema/beans,只支持bean,import,alias等基本標籤,而要支持context,aop,mvc等其餘名稱空間,就須要在xml文件中單獨聲明。mvc

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop 
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

那麼若是想自定義xml標籤,用來支持一些特殊的功能或者集成一個框架到spring中去,應該怎麼去實現呢?app

就是經過NamespaceHandler接口。來看他的定義:框架

Base interface used by the DefaultBeanDefinitionDocumentReader for handling custom namespaces in a Spring XML configuration file.dom

DefaultBeanDefinitionDocumentReader中處理xml配置文件中自定義命名空間的基本接口函數

接口中定義了三個方法,通常經常使用的只有兩個源碼分析

public interface NamespaceHandler {

	// 初始化操做,通常都是註冊子標籤的解析器BeanDefinitionParser
	void init();

	// 解析標籤的具體操做
	BeanDefinition parse(Element element, ParserContext parserContext);
}

下面經過一個demo來看如何使用自定義xml標籤

自定義xml標籤

咱們想實現一個相似於<bean>標籤功能的自定義標籤,像這樣的:

<sample:entity id="sampleBean" class="com.lcifn.spring.demo.namespace.SampleBean"/>

id就是bean的名稱,class是bean的全路徑包名。而後經過ApplicationContext.getBean("sampleBean")就能夠獲取bean的實例對象。

首先要寫一個sample.xsd文件,用來定義xml的組織結構。

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.lcifn.com/sample"
		xmlns:xsd="http://www.w3.org/2001/XMLSchema"
		targetNamespace="http://www.lcifn.com/sample"
		elementFormDefault="qualified"
		attributeFormDefault="unqualified">
    <xsd:element name="entity">
        <xsd:complexType>
    		<xsd:attribute name="id" type="xsd:string" />
    		<xsd:attribute name="class" type="xsd:string" />
    	</xsd:complexType>
    </xsd:element>
</xsd:schema>

這裏聲明瞭命名空間爲http://www.lcifn.com/sample,並定義了元素entity,以及它的兩個屬性id和class。

而後在classpath下建立一個META-INF文件夾,並建立spring.handlers和spring.schemas兩個文件,這兩個文件是模仿spring本身的jar包下的寫法,打開spring-beans或者spring-context的jar包,都會有一個META-INF的文件夾,其中就有spring.handlers和spring.schemas兩個文件。

spring.schemas用來定義xml文件schemaLocation聲明的xsd和本地xsd文件的關係,好比spring中context的schemaLocation以下

xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd"

一般解析xml是會去請求http://www.springframework.org/schema/context/spring-context-3.0.xsd這個遠程文件,但每每程序運行時未必能訪問網絡,若是能夠訪問本地的xsd文件就能夠解決這個問題。因而就有了spring.schemas文件。好比spring-context的jar包下的spring.schemas定義了context的xsd文件關係。

http\://www.springframework.org/schema/context/spring-context-4.1.xsd=org/springframework/context/config/spring-context-4.1.xsd

這裏的value值時classpath下本地xsd文件的包路徑名。

剛剛寫的sample.xsd我放在了com.lcifn.spring.demo.xsd包路徑下,所以咱們的spring.schemas以下:

http\://www.lcifn.com/sample.xsd=com/lcifn/spring/demo/xsd/sample.xsd

spring.handlers是spring定義的xml命名空間和其對應的處理器的映射,好比context的:

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler

ContextNamespaceHandler繼承了NamespaceHandlerSupport類,用來對context命名空間的xml標籤進行解析,並與spring ioc容器進行交互(如註冊BeanDefinition等)。所以咱們也須要定義一個NamespaceHandlerSupport的子類SampleNamespaceHandler,並在spring.handlers中與sample的命名空間進行關聯。

SampleNamespaceHandler.java

package com.lcifn.spring.demo.namespace;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class SampleNamespaceHandler extends NamespaceHandlerSupport{

	public void init() {
		registerBeanDefinitionParser("entity", new EntityBeanDefinitionParse());
	}

}

實現init方法,註冊entity標籤的解析器EntityBeanDefinitionParse。

EntityBeanDefinitionParse.java

package com.lcifn.spring.demo.namespace;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;

public class EntityBeanDefinitionParse implements BeanDefinitionParser{

	public BeanDefinition parse(Element element, ParserContext parserContext) {
		String id = element.getAttribute("id");
		String beanClassName = element.getAttribute("class");
		BeanDefinition definition = new RootBeanDefinition();
		definition.setBeanClassName(beanClassName);
		BeanDefinitionReaderUtils.registerBeanDefinition(new BeanDefinitionHolder(definition, id), parserContext.getRegistry());
		return definition;
	}
}

EntityBeanDefinitionParse實現spring的BeanDefinitionParser接口,在parse方法裏獲取entity的id和class屬性,並實例化一個RootBeanDefinition,而後註冊到spring容器中。

定義一個SampleBean類,只有一個print方法

package com.lcifn.spring.demo.namespace;

public class SampleBean {

	public void print(){
		System.out.println("sample bean works");
	}
}

定義namespace-handler.xml配置文件,要聲明xmlns:sample,以及在xsi:schemaLocation中指定sample的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:sample="http://www.lcifn.com/sample"
	xsi:schemaLocation="
	http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	http://www.lcifn.com/sample
	http://www.lcifn.com/sample.xsd">
	
	<sample:entity id="sampleBean" class="com.lcifn.spring.demo.namespace.SampleBean"/>
</beans>

再寫一個Test類

package com.lcifn.spring.demo.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.lcifn.spring.demo.namespace.SampleBean;

public class SampleNamespaceHandlerTest {

	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("namespace-handler.xml");
		SampleBean sampleBean = context.getBean("sampleBean",SampleBean.class);
		sampleBean.print();
	}
}

運行後顯示:

sample bean works

表示咱們自定義的entity被spring解析並加載到容器中了。

源碼分析

spring源碼-IOC容器(二)-Bean的定位解析註冊中講解了spring是如何解析xml文件的,但主要就bean標籤的解析進行了詳解。這裏咱們來分析自定義標籤的解析過程。

1.加載spring.schemas

spring在建立DOM解析的DocumentBuilder時,設置了EntityResolver的實現類ResourceEntityResolver

XmlBeanDefinitionReader.getEntityResolver()

ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader != null) {
	this.entityResolver = new ResourceEntityResolver(resourceLoader);
}

DefaultDocumentLoader.createDocumentBuilder()

DocumentBuilder docBuilder = factory.newDocumentBuilder();
if (entityResolver != null) {
	docBuilder.setEntityResolver(entityResolver);
}

而ResourceEntityResolver中的resolveEntity方法先調用其父類DelegatingEntityResolver的方法

DelegatingEntityResolver.resolveEntity()

if (systemId != null) {
	if (systemId.endsWith(DTD_SUFFIX)) {
		return this.dtdResolver.resolveEntity(publicId, systemId);
	}
	else if (systemId.endsWith(XSD_SUFFIX)) {
		return this.schemaResolver.resolveEntity(publicId, systemId);
	}
}

如今的spring都使用xsd文件,而這裏的schemaResolver在構造函數中默認設置爲PluggableSchemaResolver

this.schemaResolver = new PluggableSchemaResolver(classLoader);

而PluggableSchemaResolver的resolveEntity方法中調用getSchemaMappings()返回xsd文件的路徑

PluggableSchemaResolver.resolveEntity()

if (systemId != null) {
	String resourceLocation = getSchemaMappings().get(systemId);
	if (resourceLocation != null) {
		Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
		try {
			InputSource source = new InputSource(resource.getInputStream());
			source.setPublicId(publicId);
			source.setSystemId(systemId);
			if (logger.isDebugEnabled()) {
				logger.debug("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
			}
			return source;
		}
		catch (FileNotFoundException ex) {
			if (logger.isDebugEnabled()) {
				logger.debug("Couldn't find XML schema [" + systemId + "]: " + resource, ex);
			}
		}
	}
}

第一次調用getSchemaMappings()時會去加載全部META-INF/spring.schemas配置,其中的schemaMappingsLocation默認爲DEFAULT_SCHEMA_MAPPINGS_LOCATION

PluggableSchemaResolver	

public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";

private Map<String, String> getSchemaMappings() {
	if (this.schemaMappings == null) {
		synchronized (this) {
			if (this.schemaMappings == null) {
				if (logger.isDebugEnabled()) {
					logger.debug("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
				}
				try {
					Properties mappings =
							PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
					if (logger.isDebugEnabled()) {
						logger.debug("Loaded schema mappings: " + mappings);
					}
					Map<String, String> schemaMappings = new ConcurrentHashMap<String, String>(mappings.size());
					CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);
					this.schemaMappings = schemaMappings;
				}
				catch (IOException ex) {
					throw new IllegalStateException(
							"Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex);
				}
			}
		}
	}
	return this.schemaMappings;
}

至此spring.schemas在xml解析前就已經配置到DocumentBuilder中

2.加載spring.handlers

在DefaultBeanDefinitionDocumentReader類parseBeanDefinitions方法中,解析每個element,也就是每個標籤時,會先判斷element是否屬於默認的命名空間。

DefaultBeanDefinitionDocumentReader

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

若是不是默認的命名空間,就調用BeanDefinitionParserDelegate的parseCustomElement方法,來解析出自定義命名空間的處理器,即NamespaceHandler的實現類。

BeanDefinitionParserDelegate.java

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

spring默認的NamespaceHandlerResolver爲DefaultNamespaceHandlerResolver,而在resolve方法中調用getHandlerMappings方法,而後匹配傳入的namespaceUri來獲取NamespaceHandler的實例或是其className。

DefaultNamespaceHandlerResolver.java

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

getHandlerMappings方法第一次調用時,加載META-INF/spring.handlers配置

DefaultNamespaceHandlerResolver.java

private Map<String, Object> getHandlerMappings() {
	if (this.handlerMappings == null) {
		synchronized (this) {
			if (this.handlerMappings == null) {
				try {
					Properties mappings =
							PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
					if (logger.isDebugEnabled()) {
						logger.debug("Loaded NamespaceHandler mappings: " + mappings);
					}
					Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
					CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
					this.handlerMappings = handlerMappings;
				}
				catch (IOException ex) {
					throw new IllegalStateException(
							"Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
				}
			}
		}
	}
	return this.handlerMappings;
}

至此就解析到全部的spring.handlers並拿到自定義的NamespaceHandler實現類的實例。

3.NamespaceHandlerSupport

在BeanDefinitionParserDelegate的parseCustomElement方法中,調用NamespaceHandler的parse方法,並傳入要解析的element和上下文信息(其中包含BeanFactory的實例)。

spring爲咱們封裝了一個支持類NamespaceHandlerSupport,對parse方法進行了處理,拿到子標籤的名稱(好比上面sample:entity的entity),而後匹配到自定義的BeanDefinitionParser實現類(好比上面的EntityBeanDefinitionParse),調用其parse方法進行真正的解析。

NamespaceHandlerSupport.java

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

其中的this.parsers就是BeanDefinitionParser的一個Map集,經過在init方法中註冊進來。好比上面SampleNamespaceHandler的init方法

public void init() {
	registerBeanDefinitionParser("entity", new EntityBeanDefinitionParse());
}

而registerBeanDefinitionParser就是將自定義的BeanDefinitionParser實現扔到parsers中去

protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
	this.parsers.put(elementName, parser);
}

至此整個自定義xml的流程就講解完了。

相關文章
相關標籤/搜索