spring 对于 XML 中自定义标签的解析

我们知道,在 spring 的配置文件解析时,通过 XmlBeanDefinitionReader 来操作,下面来看看对 XML 配置文件解析时,这个类具体是如何操作的。

入口

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
		throws BeanDefinitionStoreException {

	Document doc = doLoadDocument(inputSource, resource);
	int count = registerBeanDefinitions(doc, resource);
	return count;
}

核心逻辑就两行:

第一行,将 xml 转换为 org.w3c.dom.Document,方便后续 registerBeanDefinitions 操作,转换使用 JAXP 中的 DOM 解析来操作。

第二行,从名字可以看出,注册所有 BeanDefinition,这也是接下来的重点。

registerBeanDefinitions

要注册 BeanDefinition,就需要先解析出 BeanDefinition。

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
	BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
	int countBefore = getRegistry().getBeanDefinitionCount();
	documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
	return getRegistry().getBeanDefinitionCount() - countBefore;
}

先创建一个 BeanDefinitionDocumentReader,此处为 DefaultBeanDefinitionDocumentReader,接着通过这个类来发起注册调用,在这个之前,调用 createReaderContext 来创建 XmlReaderContext。

XmlReaderContext

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);
}
public DefaultNamespaceHandlerResolver() {
	this(null, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}

逻辑很简单,由于 spring 遵循 SRP(单一职责原则),所以类会比较多。

这个 DefaultNamespaceHandlerResolver 就是完成自定义标签扩展及解析很重要的一个类。在DEFAULT_HANDLER_MAPPINGS_LOCATION,即 META-INF/spring.handlers 中会存放自定义标签相应的解析器,以 key=value 形式存放,其中 key 为 namespaceUri,value 为对应的解析器的全限定性类名,形式如下:

http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

这里就完成了 XmlReaderContext 的创建,接下来通过 documentReader 发起调用。

DefaultBeanDefinitionDocumentReader

protected void doRegisterBeanDefinitions(Element root) {
	
	this.delegate = createDelegate(getReaderContext(), root, parent);

	preProcessXml(root);
	parseBeanDefinitions(root, this.delegate);
	postProcessXml(root);

	this.delegate = parent;
}

核心逻辑:

  1. 创建 delegate,BeanDefinitionParserDelegate
  2. 真正的解析 parseBeanDefinitions
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);
	}
}

根据是否为默认名命空间,将解析分为两个分支

  • parseDefaultElement
  • parseCustomElement

parseDefaultElement 

public boolean isDefaultNamespace(@Nullable String namespaceUri) {
	return !StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri);
}

默认的名命空间有两个条件

  • namespeceUri 不为 null 或 ""
  • 为:"http://www.springframework.org/schema/beans"
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
	if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
		importBeanDefinitionResource(ele);
	}
	else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
		processAliasRegistration(ele);
	}
	else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
		processBeanDefinition(ele, delegate);
	}
	else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
		// recurse
		doRegisterBeanDefinitions(ele);
	}
}

 默认标签共四个:import、alias、bean、beans。

parseCustomElement

@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
	String namespaceUri = getNamespaceURI(ele);
	if (namespaceUri == null) {
		return null;
	}
	NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
	if (handler == null) {
		error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
		return null;
	}
	return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

核心逻辑:

先利用 XmlReaderContext 中创建的 DefaultNamespaceHandlerResolver 解析 namespaceUri,获取对应的 NamespaceHandler。

public NamespaceHandler resolve(String namespaceUri) {
	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.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);
		}
	}
}

核心逻辑:

调用 getHandlerMappings,加载 handlerMappingsLocation 下配置的 spring.handlers,得到 java.util.Properties,处理成 Map,赋值给 handlerMappings,接着根据 namespaceUri 从 handlerMappings 中去拿,第一次如果能拿到,为一个字符串,即配置的解析器的全限定性类名,加载这个类得到 Class,接着实例化一个 NamespaceHandler 对象,调用 init 方法,这样就完成了自定义解析器的创建。

此处有一个优化,就是类的加载和实例化只执行一次,创建完实例对象后会放入 handlerMappings,将原有的 value 值全限定性类名,替换为具体的实例对象,下一次遇到同样的namespaceUri 时,执行 resolve,直接从 handlerMappings 中查找到 NamespaceHandler 对象就返回了。

得到这个 NamespaceHandler,调用 parse 方法,完成具体对应标签的解析。

示例

以 aop 标签为例,下面来看看对应的 AopNamespaceHandler 及解析方法。先来看看这个标签下的元素名称。

public void init() {
	// In 2.0 XSD as well as in 2.5+ XSDs
	registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
	registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
	registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());

	// Only in 2.0 XSD: moved to context namespace in 2.5+
	registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}

每个标签下,不同的元素名,匹配不同的 Parser,具体定义了几个 Parser,都在此处进行实例化创建,并将这些 BeanDefinitionParser 注册到父类 NamespaceHandlerSupport 的 parsers 中,方便后续使用。

这里有一点注意,就是实际扩展自定义标签,实现自定义标签 NamespaceHandler 时,并不需要直接实现 NamespaceHandler 接口,继承 NamespaceHandlerSupport 即可。

以 "aspectj-autoproxy" 为例。

<aop:aspectj-autoproxy/>

解析时,遇到这个标签,调用 parseCustomElement,接着拿到具体的 handler,发起 parse 调用,调入 NamespaceHandlerSupport#parse 中,根据解析出的节点名 aspectj-autoproxy,获取对应的 BeanDefinitionParser,此时为 AspectJAutoProxyBeanDefinitionParser。

@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
	AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
	extendBeanDefinition(element, parserContext);
	return null;
}
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
		ParserContext parserContext, Element sourceElement) {

	BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
			parserContext.getRegistry(), parserContext.extractSource(sourceElement));
	useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
	registerComponentIfNecessary(beanDefinition, parserContext);
}

创建 XmlReaderContext 时传入的 sourceExtractor 为 NullSourceExtractor,调用 extractSource 方法后返回 null。 

@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
		BeanDefinitionRegistry registry, @Nullable Object source) {

	return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}
@Nullable
private static BeanDefinition registerOrEscalateApcAsRequired(
		Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {

	Assert.notNull(registry, "BeanDefinitionRegistry must not be null");

	if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
		BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
		if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
			int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
			int requiredPriority = findPriorityForClass(cls);
			if (currentPriority < requiredPriority) {
				apcDefinition.setBeanClassName(cls.getName());
			}
		}
		return null;
	}

	RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
	beanDefinition.setSource(source);
	beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
	beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
	registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
	return beanDefinition;
}

可以看到,注册了一个名称为 AUTO_PROXY_CREATOR_BEAN_NAME 的 BeanDefinition。

private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {
	if (sourceElement != null) {
		boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
		if (proxyTargetClass) {
			AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
		}
		boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
		if (exposeProxy) {
			AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
		}
	}
}

 判断标签中是否存在 proxy-target-class、expose-proxy 两个属性,存在,添加到 BeanDefinition 的 PropertyValues 中。其中 proxy-target-class 对应 ProxyConfig.proxyTargetClass 属性,控制在存在接口时,是否采用 Cglib 作为切面增强。expose-proxy 对应 exposeProxy 属性,控制在执行拦截方法 interceptor 时是否将当前 proxy 暴露出去,即是否执行AopContext.setCurrentProxy(proxy);。

private void extendBeanDefinition(Element element, ParserContext parserContext) {
	BeanDefinition beanDef =
			parserContext.getRegistry().getBeanDefinition(AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME);
	if (element.hasChildNodes()) {
		addIncludePatterns(element, parserContext, beanDef);
	}
}

private void addIncludePatterns(Element element, ParserContext parserContext, BeanDefinition beanDef) {
	ManagedList<TypedStringValue> includePatterns = new ManagedList<>();
	NodeList childNodes = element.getChildNodes();
	for (int i = 0; i < childNodes.getLength(); i++) {
		Node node = childNodes.item(i);
		if (node instanceof Element) {
			Element includeElement = (Element) node;
			TypedStringValue valueHolder = new TypedStringValue(includeElement.getAttribute("name"));
			valueHolder.setSource(parserContext.extractSource(includeElement));
			includePatterns.add(valueHolder);
		}
	}
	if (!includePatterns.isEmpty()) {
		includePatterns.setSource(parserContext.extractSource(element));
		beanDef.getPropertyValues().add("includePatterns", includePatterns);
	}
}

可以看到 extendBeanDefinition 方法的作用就是解析 <aop:aspectj-autoproxy> 的子标签 <aop:include name=""/>,最后将其作为 includePatterns 添加进 BeanDefinition 的 PropertyValues。

顺便说一句,dubbo 框架中的自定义标签 <dubbo:xxx> 就是自定义标签扩展的一个应用。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

潭影空人心

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值