Spring源码-IOC容器(八)-NamespaceHandler与自定义xml
2017-07-06 00:00
537 查看
Spring IOC容器 源码解析系列,建议大家按顺序阅读,欢迎讨论
(spring源码均为4.1.6.RELEASE版本)
Spring源码-IOC容器(一)-构建简单IOC容器
Spring源码-IOC容器(二)-Bean的定位解析注册
Spring源码-IOC容器(三)-GetBean
Spring源码-IOC容器(四)-FactoryBean
Spring源码-IOC容器(五)-Bean的初始化
Spring源码-IOC容器(六)-bean的循环依赖
Spring源码-IOC容器(七)-ApplicationContext
Spring源码-IOC容器(八)-NamespaceHandler与自定义xml
Spring源码-IOC容器(九)-Component-Scan源码解析
Spring源码-IOC容器(十)-@Autowired解析
而普通的
因为spring的xml文件默认的名称空间(namespace)是http://www.springframework.org/schema/beans,只支持bean,import,alias等基本标签,而要支持context,aop,mvc等其他名称空间,就需要在xml文件中单独声明。
那么如果想自定义xml标签,用来支持一些特殊的功能或者集成一个框架到spring中去,应该怎么去实现呢?
就是通过NamespaceHandler接口。来看他的定义:
Base interface used by the DefaultBeanDefinitionDocumentReader for handling custom namespaces in a Spring XML configuration file.
DefaultBeanDefinitionDocumentReader中处理xml配置文件中自定义命名空间的基本接口
接口中定义了三个方法,一般常用的只有两个
下面通过一个demo来看如何使用自定义xml标签
id就是bean的名称,class是bean的全路径包名。然后通过ApplicationContext.getBean("sampleBean")就可以获取bean的实例对象。
首先要写一个sample.xsd文件,用来定义xml的组织结构。
这里声明了命名空间为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如下
通常解析xml是会去请求http://www.springframework.org/schema/context/spring-context-3.0.xsd这个远程文件,但往往程序运行时未必能访问网络,如果可以访问本地的xsd文件就可以解决这个问题。于是就有了spring.schemas文件。比如spring-context的jar包下的spring.schemas定义了context的xsd文件关系。
这里的value值时classpath下本地xsd文件的包路径名。
刚刚写的sample.xsd我放在了com.lcifn.spring.demo.xsd包路径下,因此我们的spring.schemas如下:
spring.handlers是spring定义的xml命名空间和其对应的处理器的映射,比如context的:
ContextNamespaceHandler继承了NamespaceHandlerSupport类,用来对context命名空间的xml标签进行解析,并与spring ioc容器进行交互(如注册BeanDefinition等)。因此我们也需要定义一个NamespaceHandlerSupport的子类SampleNamespaceHandler,并在spring.handlers中与sample的命名空间进行关联。
SampleNamespaceHandler.java
实现init方法,注册entity标签的解析器EntityBeanDefinitionParse。
EntityBeanDefinitionParse.java
EntityBeanDefinitionParse实现spring的BeanDefinitionParser接口,在parse方法里获取entity的id和class属性,并实例化一个RootBeanDefinition,然后注册到spring容器中。
定义一个SampleBean类,只有一个print方法
定义namespace-handler.xml配置文件,要声明xmlns:sample,以及在xsi:schemaLocation中指定sample的xsd
再写一个Test类
运行后显示:
sample bean works
表示我们自定义的entity被spring解析并加载到容器中了。
而ResourceEntityResolver中的resolveEntity方法先调用其父类DelegatingEntityResolver的方法
现在的spring都使用xsd文件,而这里的schemaResolver在构造函数中默认设置为PluggableSchemaResolver
而PluggableSchemaResolver的resolveEntity方法中调用getSchemaMappings()返回xsd文件的路径
第一次调用getSchemaMappings()时会去加载所有META-INF/spring.schemas配置,其中的schemaMappingsLocation默认为DEFAULT_SCHEMA_MAPPINGS_LOCATION
至此spring.schemas在xml解析前就已经配置到DocumentBuilder中
如果不是默认的命名空间,就调用BeanDefinitionParserDelegate的parseCustomElement方法,来解析出自定义命名空间的处理器,即NamespaceHandler的实现类。
spring默认的NamespaceHandlerResolver为DefaultNamespaceHandlerResolver,而在resolve方法中调用getHandlerMappings方法,然后匹配传入的namespaceUri来获取NamespaceHandler的实例或是其className。
getHandlerMappings方法第一次调用时,加载META-INF/spring.handlers配置
至此就解析到所有的spring.handlers并拿到自定义的NamespaceHandler实现类的实例。
spring为我们封装了一个支持类NamespaceHandlerSupport,对parse方法进行了处理,拿到子标签的名称(比如上面sample:entity的entity),然后匹配到自定义的BeanDefinitionParser实现类(比如上面的EntityBeanDefinitionParse),调用其parse方法进行真正的解析。
其中的this.parsers就是BeanDefinitionParser的一个Map集,通过在init方法中注册进来。比如上面SampleNamespaceHandler的init方法
而registerBeanDefinitionParser就是将自定义的BeanDefinitionParser实现扔到parsers中去
至此整个自定义xml的流程就讲解完了。
(spring源码均为4.1.6.RELEASE版本)
Spring源码-IOC容器(一)-构建简单IOC容器
Spring源码-IOC容器(二)-Bean的定位解析注册
Spring源码-IOC容器(三)-GetBean
Spring源码-IOC容器(四)-FactoryBean
Spring源码-IOC容器(五)-Bean的初始化
Spring源码-IOC容器(六)-bean的循环依赖
Spring源码-IOC容器(七)-ApplicationContext
Spring源码-IOC容器(八)-NamespaceHandler与自定义xml
Spring源码-IOC容器(九)-Component-Scan源码解析
Spring源码-IOC容器(十)-@Autowired解析
前言
在spring的xml配置文件中,当我们想使用spring-context或者spring-aop的配置时,都是有前缀的标签。如:<context:component-scan/> <aop:config/>
而普通的
<bean>标签则是没有前缀的,为什么会有这样的区别呢?
因为spring的xml文件默认的名称空间(namespace)是http://www.springframework.org/schema/beans,只支持bean,import,alias等基本标签,而要支持context,aop,mvc等其他名称空间,就需要在xml文件中单独声明。
<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中去,应该怎么去实现呢?
就是通过NamespaceHandler接口。来看他的定义:
Base interface used by the DefaultBeanDefinitionDocumentReader for handling custom namespaces in a Spring XML configuration file.
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的实现类ResourceEntityResolverXmlBeanDefinitionReader.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的流程就讲解完了。
相关文章推荐
- 【spring源码学习】spring的IOC容器之自定义xml配置标签扩展namspaceHandler向IOC容器中注册bean
- 通过DefaultListableBeanFactory加载.xml配置文件学习Spring-IoC容器注册/加载bean的机制(源码走读)
- Unable to locate Spring NamespaceHandler for XML schema namespace [http://ww
- 【Spring】IOC核心源码学习(二):容器初始化过程
- Spring源码分析----IoC容器(二)
- 初始化IoC容器(Spring源码阅读)
- SPRING源码分析:IOC容器
- Unable to locate Spring NamespaceHandler for XML schema namespace 【http://www.springframework.org/schema/context】
- maven spring-security3 jar包 Configuration problem: Unable to locate Spring NamespaceHandler for XML
- Spring源码阅读之IoC容器初始化1 -- Resource定位
- spring报错Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace
- 初始化IoC容器(Spring源码阅读)-我们到底能走多远系列(31)
- Spring源码学习-容器初始化之FileSystemXmlApplicationContext(二)路径格式及解析方式(上) 推荐
- Spring源码阅读之IoC容器初始化2 -- BeanDefinition载入与解析
- Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/aop]
- Unable to locate Spring NamespaceHandler for XML schema namespace [http://cxf.apache.org/jaxws]
- Spring源码解析-BeanDefinition在IOC容器中的注册(三)
- Spring IoC容器在Web环境中的启动源码阅读
- SPRING源码分析:IOC容器
- Spring源码阅读之IoC容器初始化3 -- BeanDefinition在IoC容器中的注册