Spring 源码深入解析(3)之自定标签的解析

     首先感谢 《Spring源码深度解析》郝佳,让我对spring源码有了更深的理解,本篇文章主要是对《Spring源码深度解析》解读的笔记以及自己对书本解读后的理解

      在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)) {
						//默认标签的解析
						parseDefaultElement(ele, delegate);
					}
					else {
						//自定义标签的解析
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			delegate.parseCustomElement(root);
		}
	}

      以上代码可以看出,当Spring 拿到一个元素是首先要做的是根据命名空间进行解析,如果是默认的命名空间则使用parseDefaultElement
方法去解析。否则使用parseCustomElement方法去解析
      
      1、自定义标签的使用
a:使用步骤(前提导入Spring的Core包加入到项目中)
  ①:创建一个需要扩展的组件
  ②:定义一个XSD文件描述组件内容
  ③:创建一个文件,实现BeanDefinitionParser接口,用来解析XSD 文件中的定义个组件定义
  ④:创建一个Handler文件,扩展自NamespaceHandlerSupport,目的是将组件注册到Spring容器
  ⑤:编写Spring.Handler和Spring.schemas。

b:编写使用自定义标签的案例

  ①:创建一个POJO,用来接收配置文件 

public class User{
	private String userName;
	private String email;
	//省略set、get方法
 }

           ②:定义一个XSD文件描述组件内容

<?xml version="1.0" encoding="UTF-8"?>
    <schema xmlns="http://www.w3.org/2001/XMLSchema"
	 targetNamespace="http://www.lexuba.com/schema/user"
	 xmlns:tns="htttp://www.lexueba.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>
    </schena>

          上面的XSD文件中描述了一个新的targetNamespace,并在这个空间定义了一个name为user的element,user有3个属性id、userName和email。

  ③:创建一个文件,实现BeanDefinitionParser接口,用来解析XCD文件中的定义和组件定义

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

          ④:创建一个Handler 文件,扩展自NamespaceHandlerSupport,目的是将组件注册到Spring容器。

public class MyNamespacehandler extends NamespaceHandlerSupport{
	public void init(){
	   registerBeanDefinitionParser("user",new UserBeanDefinitionParser);
	}
 }

         以上代码很简单,无非就是遇到自定义标签<user:aaa 这样的类似于user 来头的他不是就会把这个元素能给对应的
UserBeanDefinitionParser去解析
 ⑤:编写Spring.handlers 和Spring.schemas文件,默认是在工程中的/META-INF/文件夹下,当热,你可以通过Spring的扩展或者修改源码方式改变路径。
 Spring.handlers:http\://www.lexueba.com./schema/user-test.customtag.MyNamespacehandler
 Spring.schemas:http\://www.lexuba.com/schema/user.xsd-META-INF/Spring-test.xsd
 到这里,自定义的配置就结束了,Spring 加载自定义标签的大致流程就是遇到自定义标签后就会去Spring.handlers 和Spring.schemas
中去找对应的handler 和 XSD,默认位置是/META-INF/下,进而有找到对应的handler以及解析元素的Parser,从而完成整个自定义元素的解析。

 ⑥:创建测试配置文件,在配置文件中引入对应的命名空间以及XSD

 <beans xmls="http://www.Springframework.org/schema/beans"
	xmlns:myname="http://www.lexueba.com/schema/user"
	xsi:schemaLocation="http://www.lexueba.com/schema/user http://www.lexueba.com/schema/user.xsd"
		
	<myname:user id="testbean" userName="aaa" email="bbb"/>
 </bean>

          ⑦:测试

 public static void main(String[] args){
	ApplicationContexrt bf = new ClassaPathXmlApplicationContext("test/customtag/test.xml");
        User user = (User)bf.getBean("testbean");
	System.out.println(user.getUserName+","+user.getEmail());
}

         不出意外的话在控制台就会打印出:aaa,bbb的结果,

 以上就是使用自定义标签的整个过程

 

2.自定义标签解析

   自定义标签的解析过程如下(源码解析):

public BeanDefinition parseCustomElement(Element ele) {
	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  进行解析
	return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

   2.1 获取标签的命名空间

       直接调用org.w3c.dom.Node 中已经提供的方法(getNamespaceURI),源码如下
       public String getNamespaceURI(Node node) {
return node.getNamespaceURI();

       }

 2.2 提取自定义标签处理器
有了命名空间,就进行NamespaceHandler 提取了,分析NamespaceHandler handler =   this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);

 在readerContext初始化的时候其属性 namespaceHandlerResolver已经被初始化为了DefaultNamespaceHandlerResolver   的实例,所以这里调用resove方法其实调用的是DefaultNamespaceHandlerResolver 类中的方法。源码如下:

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的初始化方法
				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);
			}
		}
	    }

     以上函数阐述了解析自定义NamespaceHandler的过程,其中必不可少的操作就是在Spring.handlers 文件中配置命名空间与命名空间处理器的映射关系,从而获取到自定义的NamespaceHandler 之后进行处理器初始化并解析。回忆之前自定义命名空间处理器的内容:

public class MyNamespacehandler extends NamespaceHandlerSupport{
       public void init(){
	    registerBeanDefinitionParser("user",new UserBeanDefinitionParser);
       }
}

     当得到自定义命名空间处理器后会马上执行 namespaceHandler.init()来进行自定义BeanDefintionParser的注册。注册后,命名空间处理器就可以根据标签的不同来调用不同解析器进行解析。所以可以推断getHandlerMapping的主要功能就是读取

Spring.handlers配置文件并将配置文件缓存到Map中。源码如下:

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

   以上代码中借助了工具类PropertiesLoaderUtils 对属性handlerMappingsLocation 进行了配置文件的读取,handlerMappingsLocation被默认初始化为“META-INF/Spring.handlers”

 

2.3 标签解析

      得到解析器以及要分析的元素后,Spring就可以将解析工作委托给自定义解析器解析。在Spring 中代码为:
      return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));

      此时handler 已经被实例化成为了我们自定义的MyNamespaceHandler了,而MyNamespaceHandler也已经完成了初始化工作,实际上自定义的空间处理器并没有实现parse方法,所以推断这个方法是在父类中实现的。源码如下:

 public BeanDefinition parse(Element element, ParserContext parserContext) {
		 //寻找解析器并进行解析操作
		 return findParserForElement(element, parserContext).parse(element, parserContext);
	      }
	      //寻找解析器
              private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
		 //根据元素名称,也就是<myname:user 中的user,若在示例中,此时localName为user
		 String localName = parserContext.getDelegate().getLocalName(element);
		 //根据user 找到对应的解析,也就是在registerBeanDefinitionParser("user",new UserBeanDefinitionParser())注册的解析器
		 BeanDefinitionParser parser = this.parsers.get(localName);
		 if (parser == null) {
			parserContext.getReaderContext().fatal(
					"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
		 }
		 return parser;
	      }
	      //解析过程
	      public final BeanDefinition parse(Element element, ParserContext parserContext) {
		AbstractBeanDefinition definition = parseInternal(element, parserContext);
		if (definition != null && !parserContext.isNested()) {
			try {
				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 转换为BeanDefinitionHolder并注册
				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;
	      }

   从以上代码可以看出,parse方法中大部分的代码是用来处理将解析后的AbstractBeanDefinition 转换为BeanDefinitionHolder 并注册功能,真正去做解析的事情委托给了函数parseInternal。在parseInternal方法中并不是直接调用自定义的doParse 函数,而是进行了一系列的数据准备,包括beanClass、scope、lazyInit等属性的准备

     parseInternal方法源码如下:

 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));
		 if (parserContext.isNested()) {
			// I若存在父类则使用父类的scope属性
			builder.setScope(parserContext.getContainingBeanDefinition().getScope());
		 }
		 if (parserContext.isDefaultLazyInit()) {
			// 延迟加载
			builder.setLazyInit(true);
		 }
		 //调用子类重写的都Parse方法进行解析
		 doParse(element, parserContext, builder);
		 return builder.getBeanDefinition();
	     }
 protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
	doParse(element, builder);
}

总结:在使用自定标签的实例中,在我们自定义的UserBeanDefinitionParser中只是做了与自己业务逻辑相关的部分。在整个自定义标签解析的过程中同样是按照Spring中默认标签
处理方式进行,包括创建BeanDefinition 以及相应默认属性设置这些工作都是由Spring给我们实现了,只是暴露处一些接口来提供用户实现个性化业务。
自定义标签解析的步骤:
    ①:获取标签的命名空间
    ②:提取自定义标签处理器
    ③:标签解析


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值