深入理解Spring源码(三)- 自定义标签

127 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情

上文中学习了Spring配置文件解析,了解了默认标签的解析流程,而Spring的理念在于有强大的扩展性,所以我们是否可以自定义自己的标签?答案是肯定的。

一、再看配置文件

上一讲已经成功解析了默认配置文件中的元素,那么回过头来看配置文件,会不难的发现配置文件最上面有一排密密麻麻的url,这排url不是随手写的属性,是对当前文档规范的定义,一种叫dtd,另一种是xsd,说白了就是认识配置文件中的一个个标签。

其核心再META-INF文件夹下的两个文件

spring.schemas

虽然通过配置文件头可以到对应的网站进行解析,如果网络不通的情况下该怎么办?那就要使用本地的解析。对应本地的解析文件就在spring.schemas文件中

在对应的xsd或dtd文件中对应着标准的规则,也就是我们在配置文件写的标签。

那么这个文件是怎么加载进来的

加载spring.schemas文件

上一讲已经将整个加载流程走了一遍,这里不在赘述,直接定位到具体的位置

在AbstractXmlApplicationContext#loadBeanDefinitions(DefaultListableBeanFactory beanFactory)

这个方法会为指定的 ResourceLoader 创建 ResourceEntityResolver

在它的super方法中会指定schemaMappingsLocation的地址

这里设置好需要解析的路径后,会在后面解析inputSource输入流为Document中进行加载

实际上按照正常的断点走过来是这样的

XmlBeanDefinitionReader#doLoadBeanDefinition(inputSource,resource) ---> 

XmlBeanDefinitionReader#doLoadDocument(inputSource,resource) --->

DefaultDocumentLoader#loadDocument(inputSource ....) 中的builder.parse方法中

到这里我在断点的时候实际上并没有发现有调用到获取这个路径下的文件的内容

反向查找一波,可以看到的是有一个方法叫做getSchemaMappings()

在这个方法中有schemaMappingLocation属性的使用,随后把断点直接打在这个方法中

可以看下堆栈信息

中间会有一堆的调用,可以直接忽略掉,最总会通过getSchemaMappings()拿到一个map,

随后会通过systemId获得具体的路径,在通过ClassPathResource解析

spring.handler

这个文件中记录着具体的标签的解析类的初始化

例如:在配置文件中写了一个<context:component-scan> 的标签,那么他就会根据

http\://www.springframework.org/schema/cont… ComponentScanBeanDefinitionParser()对应的解析器

下面可以找一下这个实在什么时候进行加载的

加载spring.handler

根据定位我们可以找到自定义的解析方法

进入解析类可以看到

这个里面有一个this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri)

在resolve中包含一个方法叫做getHandlerMappings(),其实和加载spring.schemas的道理一样,回去把所有的META-INF下的spring.handler按照map的形式进行加载,然后根据namespaceUri获得具体的NamespaceHandler

getHandlerMappings()

假设配置文件中配置了这么一个属性

<context:property-placeholder file-encoding=""></context:property-placeholder>

那么就会得到PropertyPlaceholderBeanDefinitionParser 然后执行parse进行解析

总结

总结一下,加载自定义的配置标签,很重要的是两个文件,一个spring.handler 一个spring.schemas,用一张图表示

二、实现自定义的标签

实现自定义的标签我们先要准备两个重要的文件以及xsd文件

spring.handlers

http\://www.ym.org/schema/ym=com.spring.self.YmNamespaceHandler

spring.schemas

http\://www.ym.org/schema/ym.xsd=META-INF/ym.xsd

ym.xsd

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
		targetNamespace="http://www.ym.org/schema/ym"
		xmlns:tns="http://www.ym.org/schema/ym"
		elementFormDefault="qualified">
	<element name="text">
		<complexType>
			<attribute name ="age" type = "integer"/>
			<attribute name ="name" type = "string"/>
		</complexType>
	</element>
</schema>

编写处理类YmNamespaceHandler

package com.spring.self;

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

/**
 * 将自定义的BeanDefinitionParser注册spring工厂中
 */
public class YmNamespaceHandler extends NamespaceHandlerSupport {

	@Override
	public void init() {
		registerBeanDefinitionParser("text",new YmBeanDefinitionParser());
	}
}

解析类

package com.spring.self;

import com.spring.Bean;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;

/**
 * 自定义标签的解析类
 */
public class YmBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
	protected Class<?> getBeanClass(Element element) {
		return Bean.class;
	}
	@Override
	protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
		if (element.hasAttribute("age")) {
			builder.addPropertyValue("age", element.getAttribute("age"));
		}
		if (element.hasAttribute("name")) {
			builder.addPropertyValue("name", element.getAttribute("name"));
		}
	}
}

启动类

package com.spring.self;

import com.spring.Bean;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SelfText {
	public static void main(String[] args) {
		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("text.xml");
		Bean bean = context.getBean(Bean.class);
		System.out.println(bean.toString());
	}
}

配置文件

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

	<ym:text id="ymTest" name="1" age="2"></ym:text>

</beans>

实体类

package com.spring;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

public class Bean {

	private String name;

	private Integer age;


	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	@Override
	public String toString() {
		return "Bean{" +
				"name='" + name + '\'' +
				", age=" + age +
				'}';
	}
}

以上工作完成一个简单的自定义标签类

运行结果

三、总结

spring自定义标签属性是一个强大的功能,可以利用这一特性进行自定义的扩展实现。