1.在spring中提供了标签扩展的抽象类NamespaceHandlerSupport,而Dubbo 则是在内部自定义了类DubboNamespaceHandler,继承spring 的NamespaceHandlerSupport。DubboNamespaceHandler的类继承关系如下:
2. NamespaceHandlerSupport属性parsers 介绍,parsers 是个map,在dubbo中,存放dubbo配置文件中各种配置标签对应的解析器。
private final Map parsers = new HashMap();
DubboNamespaceHandler 在初始化时候,将配置的各个标签各自注册上对应的解析器,以便后面对各个标签进行合适的解析。
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}
public void init() {
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
//dubbo 服务提供者解析器
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
//dubbo 服务消费端解析器
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));
}
}
从DubboNamespaceHandler 的解析器注册中,我们可以看到dubbo中自定义了解析器。其直接实现了spring中的接口BeanDef initionParser。核心方法当然是BeanDefinition parse(Element element, ParserContext parserContext, Class> beanClass, boolean required)。该方法将解析出BeanDefinition ,然后交给spring容器。
至此,大概知道duboo怎么扩展标签并解析成BeanDefinition 了,其实这些网上一搜一大堆。但是还远远不够。继续提出疑问,
继续剖析。。。
关于这个问题,我们还得从spring的启动说起,spring启动或者重启时候调用AbstractApplicationContext下的方法refresh(),直接上代码(spring代码嵌套很深,注意一步一步跟紧):
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.(这个方法,主要是刷新bean工厂)
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
。。。。。。。下面的代码先忽略。。。。。。。。。。。。
}
obtainFreshBeanFactory()这个方法就是我们研究的。此方法在sping启动或者重启时,都会销毁之前的bean工厂,并重新创建,进去看看。
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();//注意这个方法
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
refreshBeanFactory() 再进去这个方法看
@Override
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();//如果之前有bean工厂,则销毁,然后重新创建
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);//注意这个方法
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
loadBeanDefinitions()此方法是重点,就是来加载BeanDefinition,但是里面会嵌套特别深,中间部分省略,经过一路嵌套,终于来到DefaultBeanDefinitionDocumentReader下的parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)方法,直接看主要代码:
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)) {//判断该配置文件中的命名空间是否是spring默认的,下面是spring的配置走的
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);//直接告诉你,dubbo的配置都会走这个
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
在项目中,我们的配置文件头部定义中,定义了各个标签的命名空间url,上图中http://www.springframework.org/schema/beans是spring的命名空间url,http://code.alibabatech.com/schema/dubbo是dubbo定义扩展标签的url。命名空间url会有对应的xsd文件,是对标签的定义。在dubbo的META-INF下定义了该文件。另外还有spring.schemas ,spring.handlers文件。
其中,spring.schemas文件定义的是dubbo.xsd文件的路径,spring.handlers文件则定义了dubbo命名空间url对应的命名空间处理器,既上文说的DubboNamespaceHandler的全路径。
这下,delegate.parseCustomElement(ele); dubbo的配置都会走这个,这个应该就明白了。
绕开小插曲,我们进入主题,进入delegate.parseCustomElement(ele);
public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}
parseCustomElement((Element ele) )又调用parseCustomElement(Element ele, BeanDefinition containingBd) ;看看这个方法。
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
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));//重点来了!!!
}
看到了吗,重点来了!!!
NamespaceHandler,这不是上面刚开始说的命名空间处理器吗,对,经过NamespaceHandler handler = this.readerContext.g etNamespaceHandlerResolver().resolve(namespaceUri) 这句,namespaceUri就是上面说的命名空间url,此处就是dubbo的http://code.alibabatech.com/schema/dubbo,通过namespaceUri获取到com.alibaba.dubbo.config.spring.schem a.DubboN amespaceHandler。就是spring.handlers文件中配置的映射。
public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
好了,现在我们知道了NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);中获取的实例是DubboNamespaceHandler的实例。下来的 handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));则调用的就是DubboNamespaceHandler父类中的parse(ele, new ParserContext(this.readerContext, this, containingBd)),
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
return findParserForElement(element, parserContext).parse(element, parserContext);
}
然后findParserForElement(element, parserContext)就是从我们刚开始讲的private final Map
好了,spirng是怎么加载扩展的标签的问题解决了。
好了,就到这了,写了3个小时了,真是很费劲,满脸油,头皮发麻。。。。。。。。。。。