05-spring源码分析-Spring如何实现自定义命名空间
2019-02-01 本文已影响0人
cjxz
上一篇文章我们使用spring提供的
NamespaceHandlerSupport
和BeanDefinitionParser
自己实现了一个自定义命名空间。spring自定义命名空间demo
这一节我们来分析一下spring是如何实现这个逻辑的。
其实我们可以想一下spring提供了各种命名空间,相对的每个命名空间肯定也是通过上面两个抽象类来帮助我们实现的。下面是spring自定义的几个命名空间。可以看到她们的处理。随便看一个
http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
下面是UtilNamespaceHandler
的部分源码
public class UtilNamespaceHandler extends NamespaceHandlerSupport {
private static final String SCOPE_ATTRIBUTE = "scope";
@Override
public void init() {
registerBeanDefinitionParser("constant", new ConstantBeanDefinitionParser());
registerBeanDefinitionParser("property-path", new PropertyPathBeanDefinitionParser());
registerBeanDefinitionParser("list", new ListBeanDefinitionParser());
registerBeanDefinitionParser("set", new SetBeanDefinitionParser());
registerBeanDefinitionParser("map", new MapBeanDefinitionParser());
registerBeanDefinitionParser("properties", new PropertiesBeanDefinitionParser());
}
//....省略部分代码....
我们使用xsd文件的作用其实是限制在xml里面出现的Element能够有的属性,然后将这个属性映射到一个配置类上。比如我们后面将分析dubbo里面服务端实现原理就用到我们这节。好了我们不扯远了,现在再来看看服务器实现原理。
我们知道spring IOC容器加载的过程是从refresh
我们自定义的命名空间也是放到spring的配置文件中,所以自定命名空间也是交给refresh
方法调用执行。中间的调用过程我就省略调,直接进入处理的地方DefaultBeanDefinitionDocumentReader
下面是主要的调用链,跟着这个调用链就可以直到Spring是如何调用自定义命名空间的
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element)
org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)
org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver#resolve
org.springframework.beans.factory.xml.NamespaceHandlerSupport#parse
org.springframework.beans.factory.xml.NamespaceHandlerSupport#findParserForElement
-
doRegisterBeanDefinitions
方法主要判断命名空间如果是spring命名空间则进入spring的处理方法,如果是自定义命名空间那么调用自定义命名空间。这里的命名空间都是指
命名空间.png
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
//此处的root是:[beans:null]我们在写spring配置文件时所有的配置都是在<beans>下面
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isInfoEnabled()) {
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
//前置处理,不用管
preProcessXml(root);
//!!!!这个方法就是自定义命名空间的入口!!!!
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
- 下面是
parseBeanDefinitions
源码
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//首先判断是否符合beans的命名空间下。我们自定义的命名空间也是写在beans下面,所以会进入if逻辑
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) {
//这里的node是:myns:mybean
Element ele = (Element) node;
//下面会判断element是否是bean,如果不是就走else逻辑,很明显这里Element是myns:mybean所以会进入else逻辑
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
- 继续看
parseCustomElement
代码逻辑这个方法就是为了找到命名空间处理类
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
//先找到命名空间的url:http://xxx.xxx.com/schema/myns
String namespaceUri = getNamespaceURI(ele);
//通过配置文件spring.handlers里面加载处理类
//我们在com.spring.start.namespace.MynsNameSpaceHandler里面重写了init方法。在resolve方法里面会执行init方法
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
//这里执行parse方法,在parse方法里面会调用MybeanParser的parse方法
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
- 下面是parse方法,首先获得BeanDefinitionParser处理类,然后调用parse方法
public BeanDefinition parse(Element element, ParserContext parserContext) {
return findParserForElement(element, parserContext).parse(element, parserContext);
}
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
String localName = parserContext.getDelegate().getLocalName(element);
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
上面代码都很简单就不做说明了。
到这里应该可以看到spring是如何加载自定命名空间的。