手撕Dubbo如何利用spring的扩展生成BeanDefinition(史上最全,结合spring启动)

spring 标签扩展机制

抽象类NamespaceHandlerSupport介绍

1.在spring中提供了标签扩展的抽象类NamespaceHandlerSupport,而Dubbo 则是在内部自定义了类DubboNamespaceHandler,继承spring 的NamespaceHandlerSupport。DubboNamespaceHandler的类继承关系如下:

2. NamespaceHandlerSupport属性parsers 介绍,parsers 是个map,在dubbo中,存放dubbo配置文件中各种配置标签对应的解析器。

	private final Map<String, BeanDefinitionParser> parsers = new HashMap<String, BeanDefinitionParser>();

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));
    }
}
DubboBeanDefinitionParser介绍

从DubboNamespaceHandler 的解析器注册中,我们可以看到dubbo中自定义了解析器。其直接实现了spring中的接口BeanDef initionParser。核心方法当然是BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required)。该方法将解析出BeanDefinition ,然后交给spring容器。

至此,大概知道duboo怎么扩展标签并解析成BeanDefinition 了,其实这些网上一搜一大堆。但是还远远不够。继续提出疑问,

  1. spirng是怎么加载扩展的标签?
  2. dubbo何时将BeanDefinition 交给spring容器?

继续剖析。。。

spirng是怎么加载扩展的标签?

关于这个问题,我们还得从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);
		}
	}
spring标签扩展知识补充在这里插入图片描述

在项目中,我们的配置文件头部定义中,定义了各个标签的命名空间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文件中配置的映射。

扩展:关于为啥会在这个文件中定义,在spring中是常量定义死的,所以扩展标签时候必须是这个名称,而且必须在META-INF目录下
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<String, BeanDefinitionParser> parsers = new HashMap<String, BeanDefinitionParser>(); 中获取对应的解析器。进行对应配置标签的解析。

好了,spirng是怎么加载扩展的标签的问题解决了。

  1. dubbo何时将BeanDefinition 交给spring容器。
    这个问题有个坑,讲了这么多,都以为是解析完后直接返回的BeanDefinition 。其实这里面有个坑,看看解析的调用链,向上找到下图中建箭头的地方,发现调用的方法获取的BeanDefinition 并没用任何参数接受,这就尴尬了,那生解析生成的BeanDefinition 怎么交给spring了呢?在这里插入图片描述
    我们还得继续看下具体的解析方法。
    在这里插入图片描述
    在这里插入图片描述
    如上图,在解析同时,通过BeanDefinitionRegistry已经注册到spring容器中了。这里注意下,在<dubbo:service配置中如果没有定义beanName,则会以interface属性,既自己定义的服务接口全限定名称作为beanName。

好了,就到这了,写了3个小时了,真是很费劲,满脸油,头皮发麻。。。。。。。。。。。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值