1.0 自定义标签的解析.

  在之前的章节中,我们完成了对spring 默认标签的加载过程.那么现在我们将开始新的里程, spring 自定义标签的解析;

代码如下:

     /**
* Parse the elements at the root level in the document: "import", "alias", "bean".
*
* @param root the DOM root element of the document
*/
protected void parseBeanDefinitions(Element root,
BeanDefinitionParserDelegate delegate) {
// 对Bean的处理
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)) {
// 对Bean的处理,如果是默认
parseDefaultElement(ele, delegate);
}
else {
// 对Bean的处理,如果是自定义
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}

  本章中,所有的内容都是围绕一句代码 delegate.parseCustomElement(ele); 开始的,

 1.1.0 首先我们看看自定义标签的使用.

  1.1.1 根据Spring提供的扩展Schema 的定义,扩展一个Spring 自定义的标签配置大致需要以下几个步骤,.

    1.  创建一个需要扩展的组件.

    2.  定义一个XSD文件.

    3.  创建一个文件.实现BeanDefinitionParser 接口, 用来解析XSD文件中的定义,和主键定义.

    4.  创建一个Handler 文件,扩展NameSpaceHandleSupport,目的是将组件注册到Spring容器中.

    5.  编写Spring.handlers 和 Spring.schemas 文件

1.1.1 自定义标签的使用过程.

1. 首先我们创建一个普通的pojo

 package cn.c.bean;

 public class MJorcen {
private String name;
private String alias;
private String code; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public String getAlias() {
return alias;
} public void setAlias(String alias) {
this.alias = alias;
} public String getCode() {
return code;
} public void setCode(String code) {
this.code = code;
} }

2.定义一个xsd文件描述内容.

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.example.org/MJorcen" xmlns:tns="http://www.example.org/MJorcen"
elementFormDefault="qualified">
<xsd:element name="mjorce">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="alias" type="xsd:string"></xsd:element>
<xsd:element name="code" type="xsd:string"></xsd:element>
</xsd:sequence>
<xsd:attribute name="name " type="xsd:string"></xsd:attribute>
<xsd:attribute name="id" type="xsd:string"></xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>

注意:没有ID不放行.Spring-4.0.0;

3.创建一个类,实现beanDefinitionParse接口,用来解析xsd文件中的定义和主键定义,一般我们选择的是继承,AbstractSimpleBeanDefinitionParser或者AbstractSingleBeanDefinitionParser来实现

package cn.c.parse;

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList; import cn.c.bean.MJorcen; public class MJorcenNamespacesprase extends AbstractSimpleBeanDefinitionParser { @Override
protected Class<?> getBeanClass(Element element) {
return MJorcen.class;
} @Override
protected void doParse(Element element, ParserContext parserContext,
BeanDefinitionBuilder builder) {
String name = element.getAttribute("name");
NodeList eles = element.getChildNodes();
String alias = eles.item(0).getTextContent();
String code = eles.item(1).getTextContent();
builder.addPropertyValue("name", name);
builder.addPropertyValue("alias", alias);
builder.addPropertyValue("code", code);
} }

4.创建一个类,实现NamespaceHandlerSupport接口,目的是将组建注册到Spring 容器

package cn.c.parse;

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

public class MJorcenNamespacesHandlers extends NamespaceHandlerSupport {
public static void main(String[] args) {
System.out.println(MJorcenNamespacesHandlers.class);
} public void init() {
registerBeanDefinitionParser("mjorce", new MJorcenNamespacesprase());
}
}

5.编写spring.handlers和spring.schema文件

spring.schema

MJorcen.xsd=cn\c\xml\MJorcen.xsd

spring.handlers

http\://www.example.org/MJorcen=cn.c.parse.MJorcenNamespacesHandlers

到此,自定义的配置就结束了,spring.schema,spring.handlers中去找对应的handler 和XSD文件,默认的位置是META-INF目录下,进而找到对应的handler以及解析元素的parser,进而完成整个自定义标签的解析,也就是说自定义与Spring中的默认标准不同在于,Spring将自定义标签解析的工作委托给了用户去实现.

6.创建xml文件:

<?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:c="http://www.example.org/MJorcen"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.example.org/MJorcen MJorcen.xsd"> <c:mjorce id="mj" name="MichealJorcen">
<c:alias>jorcen</c:alias>
<c:code>088794</c:code>
</c:mjorce>
</beans>

7.创建测试文件

package cn.c.main;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; import cn.c.bean.MJorcen; public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"cn/c/xml/applicationContxt.xml");
MJorcen f = (MJorcen) ctx.getBean("MichealJorcen");
System.out.println(f);
}
}

2.0 下面,我们一起进入parseCustomElement方法,代码如下:

public BeanDefinition parseCustomElement(Element ele) {
//containingBd为父类Bean ,顶层元素设置为null
return parseCustomElement(ele, null);
}

追踪下去如下:

    /**
* Parse the merge attribute of a collection element, if any.
*/
public boolean parseMergeAttribute(Element collectionElement) {
String value = collectionElement.getAttribute(MERGE_ATTRIBUTE);
if (DEFAULT_VALUE.equals(value)) {
value = this.defaults.getMerge();
}
return TRUE_VALUE.equals(value);
} public BeanDefinition parseCustomElement(Element ele) {
//containingBd为父类Bean ,顶层元素设置为null
return parseCustomElement(ele, null);
}
//containingBd为父类Bean ,顶层元素设置为null
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
//获取对应的命名空间
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;
}
// 调用NamespaceHandler 注册的parse进行解析,[NamespaceHandlerSupport.java]
return handler.parse(ele, new ParserContext(this.readerContext, this,
containingBd));
}

其实思路很简单,无非就是根据对应的bean获取对应的命名空间,然后根据命名空间找到对应的解析器,然后根据对应的解析器进行解析,说起来容易,做起来可没那么简单,让我们来看看具体实现,

2.1.获取命名空间

标签的解析是从命名空间开始,无论是Spring默认的还是自定义的,都是以命名空间为基础的.至于如何实现,在org.w3c.dom.Node 中已经提供好的方法:

public String getNamespaceURI(Node node) {
return node.getNamespaceURI();
}

2.2 提取自定义标签处理器.

    /**
* Locate the {@link NamespaceHandler} for the supplied namespace URI from the
* configured mappings.
*
* @param namespaceUri the relevant namespace URI
* @return the located {@link NamespaceHandler}, or {@code null} if none found
*/
@Override
public NamespaceHandler resolve(String namespaceUri) {
// 获取所有已配置的handler映射
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方法.
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);
}
}
}

当得到命名空间处理器后,马上执行 init() 来注册解析器,如:

public void init() { registerBeanDefinitionParser("mjorce", new MJorcenNamespacesprase()); }

这里,你也可以注册多个解析器, 如<c:M,<c:N 等.使得c的命名空间中可以支持多种标签的解析.

  注册后,命名空间处理器就可以根据标签的不同来调用不同的解析器了.下面我们来看看getHandlerMappings方法

/**
* Load the specified NamespaceHandler mappings lazily.
*/
private Map<String, Object> getHandlerMappings() {
// 如果没有被缓存则开始缓存
if (this.handlerMappings == null) {
synchronized (this) {
if (this.handlerMappings == null) {
try {
// this.handlerMappingsLocation 在构造函数中已经被初始化=META-INF/spring.handlers
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());
//将Properties格式文件合并到Map格式的handlerMappings中
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;
}

2.3 标签解析

得到了解析器和需要分析的元素后,Spring就可以将解析工作委托给自定义的解析器了.代码如下:

// 调用NamespaceHandler 注册的parse进行解析
return handler.parse(ele, new ParserContext(this.readerContext, this,
containingBd));

解析个过程中,首先是寻找元素 对应的解析器,进而调用解析器中的parse方法.

那么结合实例,其实就是首先获取在handler中的init方法中注册的对应的Parse 实例.并调用parse方法,但是我们实现的自定义命名空间解析器并没有parse方法,所以推断,这个方法是在父类实现的.

[NamespaceHandlerSupport.java]

@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 寻找解析器,并进行解析
return findParserForElement(element, parserContext).parse(element, parserContext);
}

[NamespaceHandlerSupport.java]

  解析过程中,首先是寻找元素对应的解析器,进而调用解析器的parse 方法.

那么结合实例,其实就是首先获取在handler中的init方法中注册的对应的Parse 实例.并调用parse方法

/**
* Locates the {@link BeanDefinitionParser} from the register implementations using
* the local name of the supplied {@link Element}.
*/
private BeanDefinitionParser findParserForElement(Element element,
ParserContext parserContext) {
// 获取元素名称,也就是实例中的:<c:mjorcen 中的 mjorcen,
String localName = parserContext.getDelegate().getLocalName(element);
// 根据mjorcen找到对应的解析器,也就是:registerBeanDefinitionParser("mjorce", new MJorcenNamespacesprase());
BeanDefinitionParser parser = this.parsers.get(localName); if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]",
element);
}
return parser;
}

而对于parse方法的处理:

[AbstractBeanDefinitionParser.java]

@Override
public final BeanDefinition parse(Element element, ParserContext parserContext) {
AbstractBeanDefinition definition = parseInternal(element, parserContext);
if (definition != null && !parserContext.isNested()) {
try {
// 获取元素Id属性,如果没有,不放行
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));
}
// 将AbstractBeanDefinition 转化为 BeanDefinitionHandler
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;
}

从上面可以看到,真正的解析是委托给了函数parseInternal

  在parseInternal 中.并不是直接调用自定义的doParse 方法,而是进行了一系列的数据准备,包括对beanClass,scope,lazyInit等属性的准备.

@Override
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
String parentName = getParentName(element);
if (parentName != null) {
builder.getRawBeanDefinition().setParentName(parentName);
}
// 获取自定义标签中的class,此时会调用自定义解析器,如 , getBeanClass方法.
Class<?> beanClass = getBeanClass(element);
if (beanClass != null) {
builder.getRawBeanDefinition().setBeanClass(beanClass);
}
else {
// 如果子类没有实现getBeanClass 则尝试检查子类是否重新getBeanClassName方法.
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.
// 若存在父类,则使用父类的scope属性
builder.setScope(parserContext.getContainingBeanDefinition().getScope());
}
if (parserContext.isDefaultLazyInit()) {
// Default-lazy-init applies to custom bean definitions as well.
//延迟加载
builder.setLazyInit(true);
}
//调用子类的doParse方法.
doParse(element, parserContext, builder);
return builder.getBeanDefinition();
}

到此为止,Spring的解析工作全部结束了.接下来的任务就是如何使用这些bean .

4.4 spring-自定义标签的解析的更多相关文章

  1. Spring源码学习-容器BeanFactory(四) BeanDefinition的创建-自定义标签的解析.md

    写在前面 上文Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签对Spring默认标签的解析做了详解,在xml元素的解析中,Spri ...

  2. Spring 系列教程之自定义标签的解析

    Spring 系列教程之自定义标签的解析 在之前的章节中,我们提到了在 Spring 中存在默认标签与自定义标签两种,而在上一章节中我们分析了 Spring 中对默认标签的解析过程,相信大家一定已经有 ...

  3. 自己构建一个Spring自定义标签以及原理讲解

    平时不论是在Spring配置文件中引入其他中间件(比如dubbo),还是使用切面时,都会用到自定义标签.那么配置文件中的自定义标签是如何发挥作用的,或者说程序是如何通过你添加的自定义标签实现相应的功能 ...

  4. spring基础---->spring自定义标签(一)

    Spring具有一个基于架构的扩展机制,可以使用xml文件定义和配置bean.本博客将介绍如何编写自定义XML bean的解析器,并用实例来加以说明.其实我一直相信 等你出现的时候我就知道是你. Sp ...

  5. spring自定义标签之 自我实现

     引言: 最近心情比较难以平静,周末的两天就跑出去散心了,西湖边上走走,看日落,还是不错的.回来博客上发现,在自定义标签上,最后一步实现忘记加上了.其实,人生的路程中,我们总是实现着自我的价值,让自己 ...

  6. Spring 自定义标签配置

    前景:经常使用一些依赖于Spring的组件时,发现可以通过自定义配置Spring的标签来实现插件的注入,例如数据库源的配置,Mybatis的配置等.那么这些Spring标签是如何自定义配置的?学习Sp ...

  7. Spring自定义标签的实现

    首先 简单写下 spring xml解析的过程 通过一段简单的 调用spring代码开始 public static void main(String[] args) { ApplicationCon ...

  8. spring自定义标签之 规范定义XSD

    引言: spring的配置文件中,一切的标签都是spring定义好的.<bean/>等等,有了定义的规范,才能让用户填写的正常可用.想写自定义标签,但首先需要了解XML Schema De ...

  9. 基于Spring开发——自定义标签及其解析

    1. XML Schema 1.1 最简单的标签 一个最简单的标签,形式如: <bf:head-routing key="1" value="1" to= ...

  10. Spring自定义标签解析与实现

           在Spring Bean注册解析(一)和Spring Bean注册解析(二)中我们讲到,Spring在解析xml文件中的标签的时候会区分当前的标签是四种基本标签(import.alias ...

随机推荐

  1. Memcached学习(一)

    1.Memcached是什么? 引用维基百科上得简介,Memcached 是一套分布式的高速缓存系统,由LiveJournal的Brad Fitzpatrick开发,目前已被诸如Facebook等许多 ...

  2. (转)Facebook内部分享:26个高效工作的小技巧

    春节假期马上就要结束了,该收收心进入新一年的工作节奏了~分享 26 个高效工作的小技巧,希望对大家有所帮助~(我发现自己只有最后一条执行得很好,并且堪称完美!) 1.时间常有,时间优先. 2.时间总会 ...

  3. SEO前端优化

    精减代码 清除网页中一些冗余的代码,网上有这样的工具,可以辅助完成,如果需要的话,我们可以把代码中的注释去掉,甚至空行之类的也去掉,尽量的减少代码量,从而减小页面体积. CSS Sprites 通俗点 ...

  4. 前端性能优化工具--DOM Monster

    当我们开发web应用的时候,性能是一个永远不能回避的问题.其实对于DOM的性能调试也是一个不可或缺的过程.使用DOM monster你只需要添加到你的”书签中“,在任何需要调试的页面点击这个书签,它就 ...

  5. Spring PecClinic宠物医院---安装

    1.下载源代码 如果本地安装了Git工具,可以直接使用命令 git clone https://github.com/spring-projects/spring-petclinic.git 如果没有 ...

  6. HTML5新增结构标签

    引言 在本节中,笔者将向大家讲述三部分内容,分别介绍HTML5时代的召唤,跟HTML4的区别,以及HTML5中带来的新的结构标签. HTML5时代的召唤 HTML4与HTML5的区别 HTML5新结构 ...

  7. Cassandra1.2文档学习(16)—— 模式的变化

    参考文档:http://www.datastax.com/documentation/cassandra/1.2/webhelp/index.html#cassandra/dml/dml_schema ...

  8. linux 学习笔记2

    vi  编辑命令并查看 i 插入 esc  转换模式 shift + : x  保存并退出    q  不保存  !强制保存 五个查看命令 cat / less / more / tail / hea ...

  9. ZENCART 打开/关闭日志文件

    优秀的php开源程序很多都只带生成日志文件的功能,这类功能的开发可以帮助到站长在调试网站的时候及时的改正网站存在的错误,但是这类错误日志由来并非网站出现什么严重不可挽救的错误,大部分是一些未定义变量这 ...

  10. 35 个必须有的Bootstrap工具和生成器

    Bootstraptor If you think that bootstrap templates are not enough for you, you should go with bootst ...