IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 【Spring 源码深度解析】03 自定义标签的解析 -> 正文阅读

[Java知识库]【Spring 源码深度解析】03 自定义标签的解析

1 parseCustomElement

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

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
	//对 beans 的处理,根节点是beans
	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;
				//isDefaultNamespace :node.getNamespaceURI() 返回的uri 和
				//"http://www.springframework.org/schema/beans" 对比
				if (delegate.isDefaultNamespace(ele)) {
					//默认标签的解析 如 <bean id="test" class="test.TestBean">
					parseDefaultElement(ele, delegate);
				}
				else {
					//对bean 的处理,自定义的<tx:annotation-driven>
					delegate.parseCustomElement(ele);
				}
			}
		}
	}
	else {
		//解析自定义的标签
		delegate.parseCustomElement(root);
	}
}

上面代码可以看出,当 Spring 拿到一个元素时首先要做的是根据命名空间进行解析,如果是默认的命名空间,则使用parseDefaultElement方法进行元素解析,否则使用parseCustomElement方法进行解析。

2 自定义标签的使用

Spring 提供了可扩展的 Schema 的支持(需要 spring-core 包),解析工作主要包含以下几个步骤。

  1. 创建一个需要扩展的组件。
  2. 创建一个 XSD 文件描述组件的内容。
  3. 创建一个文件,实现 BeanDefinitionParser 接口,用来解析 XSD 文件中的定义和组件定义。
  4. 创建一个 Handler 文件,扩展自 NamespaceHandlerSupport,目的是将组件注册到 Spring 容器中。
  5. 编写 spring.handlers 和 spring.schemas 文件。

应用实例:
1)创建一个 POJO

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

	@Override
	public String toString() {
		return "User{" +
				"userName='" + userName + '\'' +
				", email='" + email + '\'' +
				'}';
	}
}

2)定义一个 XSD 文件描述组件内容
在 META-INF 下定义

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

上述 XSD 文件描述了一个新的 targetNamespace,并定义了一个 name 为 user 的 element。

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

public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

	@Override
	protected Class<?> getBeanClass(Element element) {
		return User.class;
	}

	//解析
	@Override
	protected void doParse(Element element, BeanDefinitionBuilder builder) {
		String userName = element.getAttribute("userName");
		String email = element.getAttribute("email");

		builder.addPropertyValue("userName", userName);
		builder.addPropertyValue("email", email);
	}

	@Override
	protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
		super.doParse(element, parserContext, builder);
	}
}

4)创建一个 Hadnler 文件,扩展自 NamespaceHandlerSupport,目的是将组件注册到 Spring 容器。

public class MyNamespaceHandler extends NamespaceHandlerSupport {

	@Override
	public void init() {
		//当遇到<user:xxx 开头的元素 就给 UserBeanDefinitionParser 解析
		registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
	}
}

以上代码当遇到自定义标签 <user:aa 这样类似于以 user 开头的元素,就会把这个元素委托给对应的 UserBeanDefinitionParser解析。

5)编写 spring.handlers 和 spring.schemas 文件。默认位置在 /META-INF/ 文件夹下。

spring.schemas
http\://www.lyb.xyz/schema/user.xsd=META-INF/spring-user.xsd

spring.handlers
http\://www.lyb.xyz/schema/user=org.springframework.customtag.MyNamespaceHandler

6)测试配置文件

<?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:mytag="http://www.lyb.xyz/schema/user"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.lyb.xyz/schema/user http://www.lyb.xyz/schema/user.xsd">

	<mytag:user id="userBean" userName="里斯" email="14813133"/>
</beans>

7)测试类

public class Main {

	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("test/customtag/customTag.xml");

		User userBean = (User) context.getBean("userBean");

		System.out.println(userBean);
	}
}

3 自定义标签的解析

// BeanDefinitionParserDelegate.java
public BeanDefinition parseCustomElement(Element ele) {
	return parseCustomElement(ele, null);
}

// containingBd为父类 bean, 顶层元素是 null
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
	//获取对应的命名空间
	String namespaceUri = getNamespaceURI(ele);
	if (namespaceUri == null) {
		return null;
	}
	//根据命名空间从上下文中获取 NamespaceHandler, DefaultNamespaceHandlerResolver
	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 进行解析, 调用 NamespaceHandlerSupport 的 parse 方法
	return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

上述代码的解析步骤:

  1. 获取对应的命名空间
  2. 根据命名空间从上下文中获取 NamespaceHandler
  3. 调用自定义的 NamespaceHandler 进行解析

3.1 获取标签的命名空间

标签的解析都是从命名空间的提取开始,无论是区分默认标签和自定义标签还是区分自定义标签中不同的处理器都是以标签所提供的名称空间为基础的。

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

3.2 提取自定义标签处理器

有了命名空间,就可以进行 NamespaceHandler 提取了。由this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri)该方法实现,在 readerContext 初始化的时候 namespaceHandlerResolver 已经被初始化为了 DefaultNamespaceHandlerResolver 了。

namespaceHandlerResolver 初始化:

XmlBeanDefinitionReader.java

public XmlReaderContext createReaderContext(Resource resource) {
	return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
			this.sourceExtractor, this, getNamespaceHandlerResolver());
}

public NamespaceHandlerResolver getNamespaceHandlerResolver() {
	if (this.namespaceHandlerResolver == null) {
		this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
	}
	return this.namespaceHandlerResolver;
}

protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
	ClassLoader cl = (getResourceLoader() != null ? getResourceLoader().getClassLoader() : getBeanClassLoader());
	return new DefaultNamespaceHandlerResolver(cl);
}

进入 DefaultNamespaceHandlerResolver 的 resolve 方法。

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("Could not find NamespaceHandler class [" + className +
					"] for namespace [" + namespaceUri + "]", ex);
		}
		catch (LinkageError err) {
			throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
					className + "] for namespace [" + namespaceUri + "]", err);
		}
	}
}

上述代码主要步骤:
1)获取所有已经配置的 handler 映射,懒加载, getHandlerMappings 该操作将 spring.handlers 文件中的所有映射读取放入缓存 handlerMappings 中。

private Map<String, Object> getHandlerMappings() {
	Map<String, Object> handlerMappings = this.handlerMappings;
	// double check
	if (handlerMappings == null) {
		// 同步
		synchronized (this) {
			handlerMappings = this.handlerMappings;
			// 第一次加载
			if (handlerMappings == null) {
				if (logger.isTraceEnabled()) {
					logger.trace("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]");
				}
				try {
					// META-INF/spring.handlers 默认在这个文件中
					//以 Properties 的方式读取
					Properties mappings =
							PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
					if (logger.isTraceEnabled()) {
						logger.trace("Loaded NamespaceHandler mappings: " + mappings);
					}
					handlerMappings = new ConcurrentHashMap<>(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 handlerMappings;
}

2)根据命名空间在 handlerMappings 中查找对应的信息,如果已经解析过则直接返回。
3)如果没有解析过则反射生成 NamespaceHandler 实例。
4)调用 namespaceHandler 的初始化方法,注册 parser,将 namespaceHandler 放入缓存。

3.3 标签解析

得到了解析器以及要分析的元素后, Spring 就可以将解析工作委托给自定义解析器去解析了。在 Spring 中的代码为:

return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));

parse 方法在父类 NamespaceHandlerSupport 中实现。

// NamespaceHandlerSupport.java
public BeanDefinition parse(Element element, ParserContext parserContext) {
	//查找解析器 实现了 BeanDefinitionParser
	BeanDefinitionParser parser = findParserForElement(element, parserContext);
	// AbstractBeanDefinitionParser -> AbstractSingletonBeanDefinitionParser
	return (parser != null ? parser.parse(element, parserContext) : null);
}

解析过程:
1)找到对应的 parser

private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
	//获取元素名称,即 <myname:user 中的 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;
}

2)调用 parser.parse(element, parserContext) 方法

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 = null;
			if (shouldParseNameAsAliases()) {
				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);
			//注册 holder ,放入缓存中,和默认标签注册一样
			registerBeanDefinition(holder, parserContext.getRegistry());
			if (shouldFireEvents()) {
				//需要通知监听器进行处理
				BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
				postProcessComponentDefinition(componentDefinition);
				parserContext.registerComponent(componentDefinition);
			}
		}
		catch (BeanDefinitionStoreException ex) {
			String msg = ex.getMessage();
			parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element);
			return null;
		}
	}
	return definition;
}

3)解析获得 AbstractBeanDefinition
说是对自定义配置文件的解析。但是,在这个函数中大部分的代码是用来处理将解析后的 AbstractBeanDefinition 转化为 BeanDefinitionHolder 并注册的功能,而真正去做解析的事情委托给了 parseInternal 方法。正是这句代码调用了自定义的解析函数。

protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
	//建造者模式,封装了 GenericBeanDefinition
	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));
	//父类 bean
	BeanDefinition containingBd = parserContext.getContainingBeanDefinition();
	if (containingBd != null) {
		// Inner bean definition must receive same scope as containing bean.
		//若有父类则使用父类的scope
		builder.setScope(containingBd.getScope());
	}
	if (parserContext.isDefaultLazyInit()) {
		// Default-lazy-init applies to custom bean definitions as well.
		builder.setLazyInit(true);
	}
	//调用自定义解析
	doParse(element, parserContext, builder);
	return builder.getBeanDefinition();
}

虽然在实例中我们定义 UserBeanDefinitionParser,但是在其中我们只是做了与自己业务逻辑相关的部分。在这个处理过程中同样也是按照 Spring 标准的处理方式进行,包括创建 BeanDefinition 以及进行相应默认属性的设置,并暴露出一些接口来供用户实现个性化的业务。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-01-28 11:45:25  更:2022-01-28 11:48:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:55:15-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码