Spring 源码阅读(三):自定义标签解析原理

在很多情况下,我们需要为系统提供可配置化支持。因此 spring 提供了可扩展 schema 的支持,扩展spring 自定义标签配置大致需要以下几个步骤:

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

接下就按照上面的步骤来看看 spring 自定义标签的整个过程。

1.解析自定义标签

为了扩展 xml 配置文件的标签,spring 提供了一个机制用于对自定义的元素进行解析。

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
	// 通过 w3c 获取节点所在的 namespace URI
	String namespaceUri = getNamespaceURI(ele);
	// 解析 namespace URI,获取 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));
}

方法只做了三件事:

  • 获取节点所属的 namespace URI
  • 解析 namespace URI 得到 NamespaceHandler
  • 调用 NamespaceHandler.parse() 方法解析自定义标签

那么问题来了,NamespaceHandler 是什么?官方文档是这样定义的:NamespaceHandler 是 spring 解析在 xml 配置文件配置的自定义标签的基本接口。因此可以知道我们遇到如 <context: componenet-scan /> 这样的标签时,会实现 NamespaceHandler 接口,可是 spring 怎么知道我自己的 NamespaceHandler 呢?并且 spring 在解析时怎么验证标签配置是否正确呢?是的,我们需要在 META-INF 目录添加两个文件:spring.schemas 和 spring.handlers。spring.schemas 告知 spring 标签的 xsd 文件在哪;spring.handlers 告知 spring 解析标签的 NamespaceHandler 的全路径名。形式分别如下:

http://www.springframework.org/schema/context/spring-context-4.3.xsd=org/springframework/context/config/spring-context-4.3.xsd
http://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler

从名字上看 NamespaceHandler 是处理 namespace 的,它并不直接用于解析自定义标签,它底层会委托给 BeanDefinitionParser 接口。那么 BeanDefinitionParser 又是在哪注册的呢?我们在看 NamespaceHandler 接口的文档时,注意到它的 init() 方法,**init() 方法是在解析自定义标签之前被调用。**因此我们可以在 init() 方法里面注册我们自定义的 BeanDefinitionParser 接口。
我们拿 <context: component-scan /> 举例子,看 spring 本身怎么做的。在上面说明 spring.schemas 和 spring.handlers 文件时就是拿它作为示例,因此就不再说了,同时也可以看到,此标签的 NamespaceHandler 是 org.springframework.context.config.ContextNamespaceHandler。

public class ContextNamespaceHandler extends NamespaceHandlerSupport {
	@Override
	public void init() {
		registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
		registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
		registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
		// 对 componenet-scan 的解析
		registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
		registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
	}

}
public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser {
	// ...省略字段定义
	
	@Override
	public BeanDefinition parse(Element element, ParserContext parserContext) {
		String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
		basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
		String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
				ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

		// Actually scan for bean definitions and register them.
		ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
		Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
		registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

		return null;
	}
	
	// ...省略其他方法
}

所以了解其工作原理之后,发现也并不负载,主要 Spring 帮我们定义好了开发标准,我们只需要按照它的约定去实现我们自己的逻辑就可以。如果看过 dubbo 源码的肯定也知道,dubbo 跟 spring 集成,也是因为 dubbo 定义了自己的 DubboNamespaceHandler,注册了对各个标签的解析器。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值