当完成从配置文件到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();
}
解析完成!