spring 自定义标签创建及源码解析

当完成从配置文件到Document的转换 并提取对应的root后,将开始了所有元素的解析,而在这一过程中就有了默认标签与自定义标签两种格式的区分,函数如下所示:

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)) {
						// 处理spring的默认标签
						parseDefaultElement(ele, delegate);
					}
					else {
						//处理用户自定义的标签.
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			delegate.parseCustomElement(root);
		}
	}

当spring拿到一个元素时首先要做的是根据命名空间进行解析,如果是默认的命名空间,则使用parseDefaultElement方法进行元素解析,否则使用parseCustomerElement()进行解析.而在此过程,我们必须先了解自定义标签的使用过程.

一. Spring提供了可扩展Schema的支持,扩展Spring自定义标签配置需要如下的步骤.

1. 先创建一个POJO,用来接收配置文件.

package com.hyq.test.bean;/**
 * Copyright(c) Beijing Kungeek Science & Technology Ltd.
 */
/**
 * 注释..
 * <p>  </p>
 *
 * @author hyq  heyuqiang@kungeek.com
 * @since 1.0
 */
public class User {
	
	private String userName;
	
	private String email;

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public void showMe(){
		System.out.println("I am user");
	}
}

2.定义一个XSD文件描述组件内容(Spring-test.xsd)

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
		targetNamespace="http://www.hyq.com/schema/user"
		xml:tns = "http://www.hyq.com/schema/user"
		elementFormDefault="qualified">
	<element name="user">
		<complexType>
			<attribute name="id" type="string"/>
			<attribute name="userName" type="string"/>
			<attribute name="email" type="string"/>
		</complexType>
	</element>
</schema>

XSD文件是xml dtd的替代者,使用Xml Schema语言进行编写,这里对XSD Schema不做太多解答,有兴趣的读者可参考W3school

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

package com.hyq.test.testlabel;/**
 * Copyright(c) Beijing Kungeek Science & Technology Ltd.
 */


import com.hyq.test.bean.User;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

/**
 * 注释..
 * <p>  </p>
 *
 * @author hyq  heyuqiang@kungeek.com
 * @since 1.0
 */
public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
	//Element 对应的类
	protected Class getBeanClass(Element element){
		return User.class;
	}
	
	//从element中解析并提取对应的元素
	protected void  doParse(Element element, BeanDefinitionBuilder bean){
		String userName = element.getAttribute("userName");
		String email = element.getAttribute("email");
		
		//将提取到的数据放入到BeanDefinitionBuilder中,待到完成所有的bean解析后统一注册到beanFactory中
		if(StringUtils.hasText(userName)){
			bean.addPropertyValue("userName",userName);
		}
		if (StringUtils.hasText(email)){
			bean.addPropertyValue("email",email);
		}
	}
}

4. 创建一个Handler文件 当遇到自定义标签<user:aaa 这样类似于以user开头的元素,就会把这个元素扔给对应的UserBeanDefinitionParser去解析.

package com.hyq.test.testlabel;/**
 * Copyright(c) Beijing Kungeek Science & Technology Ltd.
 */


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

/**
 * 注释..
 * <p>  </p>
 *
 * @author hyq  heyuqiang@kungeek.com
 * @since 1.0
 */
public class MyNamespaceHandler extends NamespaceHandlerSupport {

	@Override
	public void init() {
		registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
	}
}

5.编写Spring.handlers和Spring.schemas文件,默认位置是在工程的/META-INF/文件夹下

  •  Spring.handlers
  • http\://www.hyq.com/schema/user=com.hyq.test.testlabel.MyNamespaceHandler
  • Spring.schemas
  • http\://www.hyq.com/schema/user.xsd=META-INF/Spring-test.xsd

到此,自定义的配置结束.Spring加载自定义的大致流程是遇到自定义标签然后就去Spring.handlers和Spring.schemas中去找对应的handler的XSD,默认位置是/META-INF/下,进而有找到对应的handler以及解析元素的Parser,从而完成了整个自定义元素的解析.

6.创建测试配置文件,在配置文件中引入对应的命名空间以及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:myname="http://www.hyq.com/schema/user"
	   xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans
	   http://www.springframework.org/schema/beans/spring-beans.xsd
	   http://www.springframework.org/schema/context
	   http://www.springframework.org/schema/context/spring-context.xsd
       http://www.hyq.com/schema/user
       http://www.hyq.com/schema/user.xsd">

	<myname:user id="testBean" userName="aaa" email="bbb"/>

</beans>

7.测试

 不出意外 控制台上打印了:

 二. 自定义标签解析

1) 进入parseCustomerElement() 方法 该方法在类org.springframework.beans.factory.xml.BeanDefinitionParserDelegate

 2) 获取便签的命名空间

/**
	 * Get the namespace URI for the supplied node.
	 * <p>The default implementation uses {@link Node#getNamespaceURI}.
	 * Subclasses may override the default implementation to provide a
	 * different namespace identification mechanism.
	 * @param node the node
	 */
	@Nullable
	public String getNamespaceURI(Node node) {
		return node.getNamespaceURI();
	}

由org.w3c.dom.Node中提供方法实现

3)提取自定义标签处理

调用的resolve()是在org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver

 

 4)标签解析

 

而对于parse方法的处理:

 由上文可知,这个函数中大部分的代码是用来处理将解析后的AbstractBeanDefinition转化为BeanDefinitionHolder并注册的功能,而真正去做解析的事情委托给了函数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,此时会调用自定义解析器如UserBeanDefinitionParser中的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));
		BeanDefinition containingBd = parserContext.getContainingBeanDefinition();
		//若存在父类则使用父类的scope属性
		if (containingBd != null) {
			// Inner bean definition must receive same scope as containing bean.
			builder.setScope(containingBd.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();
	}

解析完成!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hyq12346

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值