dubbo源码系列3——dubbo自定义标签解析

Posted xu_jiang_hua

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了dubbo源码系列3——dubbo自定义标签解析相关的知识,希望对你有一定的参考价值。

        在梳理完dubbo spi 机制后【dubbo源码系列1——spi源码解读(上)dubbo源码系列2——spi源码解读(下)
本节开始梳理dubbo provider的启动流程,因基于dubbo源码进行研究,因此直接采用dubbo源码中dubbo-demo模块中xml配置示例作为demo。
        本节将解析dubbo是如何利用spring进行自定义标签解析并注册到容器中,本节安排如下:

  • demo
  • 解析流程分析
  • 总结

一、demo

  • 接口
public interface DemoService 

    String sayHello(String name);

    default CompletableFuture<String> sayHelloAsync(String name) 
        return CompletableFuture.completedFuture(sayHello(name));
    


  • 接口实现
public class DemoServiceImpl implements DemoService 
    private static final Logger logger = LoggerFactory.getLogger(DemoServiceImpl.class);

    @Override
    public String sayHello(String name) 
        logger.info("Hello " + name + ", request from consumer: " + RpcContext.getContext().getRemoteAddress());
        try 
            Thread.sleep(1000);
         catch (InterruptedException e) 
            e.printStackTrace();
        
        return "Hello " + name + ", response from provider: " + RpcContext.getContext().getLocalAddress();
    

    @Override
    public CompletableFuture<String> sayHelloAsync(String name) 
        CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> 
            return "async result";
        );
        return cf;
    

  • 启动
public class ApplicationP 
    public static void main(String[] args) throws Exception 
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-provider.xml");
        context.start();
    

  • 配置文件
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <dubbo:application name="demo-provider" metadata-type="remote" owner="xjh">
        <dubbo:parameter key="mapping-type" value="metadata"/>
    </dubbo:application>

    <!--zk注册中心-->
    <dubbo:registry id="registry1" address="zookeeper://127.0.0.1:2181"/>

    <!--暴露协议以及端口-->
    <dubbo:protocol name="dubbo" port="256880"/>

    <!--服务bean-->
    <bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl"/>

    <!--暴露服务-->
    <dubbo:service interface="org.apache.dubbo.demo.DemoService" timeout="300000" ref="demoService" registry="registry1">
    </dubbo:service>

</beans>

二、解析流程分析

1、重要组件

  • dubbo.xsd
    位于META-INF下面,用于校验规范dubbo标签的编写;
  • spring.schemas
http\\://dubbo.apache.org/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd
http\\://code.alibabatech.com/schema/dubbo/dubbo.xsd=META-INF/compat/dubbo.xsd

xsd规范链接与实际xsd文件存放位置映射关系

  • spring.handlers
http\\://dubbo.apache.org/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
http\\://code.alibabatech.com/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler

标签命名空间与实际解析handler映射关系

2、流程分析

->AbstractApplicationContext#refresh
 ->AbstractApplicationContext#obtainFreshBeanFactory
	->AbstractRefreshableApplicationContext#refreshBeanFactory
		->AbstractXmlApplicationContext#loadBeanDefinitions
			->AbstractXmlApplicationContext#loadBeanDefinitions
				->AbstractBeanDefinitionReader#loadBeanDefinitions
					->XmlBeanDefinitionReader#loadBeanDefinitions
						->XmlBeanDefinitionReader#doLoadBeanDefinitions
							->XmlBeanDefinitionReader#registerBeanDefinitions
								->DefaultBeanDefinitionDocumentReader#registerBeanDefinitions
									->DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
								

spring加载xml并解析成beanDefinition流程比较长,本节重点关注spring如何解析自定义标签解生成beanDefintion并注册到容器。

  • DefaultBeanDefinitionDocumentReader#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;
					//判断节点的命名空间是否为默认的beans,如果不是则走自定义解析
					if (delegate.isDefaultNamespace(ele)) 
						parseDefaultElement(ele, delegate);
					
					else 
						delegate.parseCustomElement(ele);
					
				
			
		
		else 
			delegate.parseCustomElement(root);
		
	

比如dubbo:application节点如下:

  • BeanDefinitionParserDelegate#parseCustomElement
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) 
        //获取节点的命名空间
		String namespaceUri = getNamespaceURI(ele);
		if (namespaceUri == null) 
			return null;
		
		  //this.readerContext.getNamespaceHandlerResolver()为DefaultNamespaceHandlerResolver
		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));
	
  • DefaultNamespaceHandlerResolver#resolve
	public NamespaceHandler resolve(String namespaceUri) 
	    //加载classpath下面所有的META-INF/spring.handlers文件得到命名空间与handler的映射,如下截图所示
		Map<String, Object> handlerMappings = getHandlerMappings();
		//获取handler
		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");
				
			    //实例化handler,dubbo对应为DubboNamespaceHandler
				NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
				//执行init方法
				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);
			
		
	
  • DefaultNamespaceHandlerResolver#getHandlerMappings
private Map<String, Object> getHandlerMappings() 
		Map<String, Object> handlerMappings = this.handlerMappings;
		if (handlerMappings == null) 
			synchronized (this) 
				handlerMappings = this.handlerMappings;
				if (handlerMappings == null) 
					try 
				     	//加载META-INF/spring.handlers文件
						Properties mappings =
								PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);

						handlerMappings = new ConcurrentHashMap<>(mappings.size());
		
			                //复制properties到map中
							CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
						this.handlerMappings = handlerMappings;
					
					catch (IOException ex) 
						throw new IllegalStateException(
								"Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
					
				
			
		
		return handlerMappings;
	


可以命名空间为http://dubbo.apache.org/schema/dubbo的标签应该用DubboNamespaceHandler解析;

  • DubboNamespaceHandler#init
    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("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true));
        registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true));
        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.class, true));
        registerBeanDefinitionParser("ssl", new DubboBeanDefinitionParser(SslConfig.class, true));
        registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, true));
        registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
    
  • DubboNamespaceHandler#parse
    public BeanDefinition parse(Element element, ParserContext parserContext) 
        //获取注册接口
        BeanDefinitionRegistry registry = parserContext.getRegistry();
        
       
       registerAnnotationConfigProcessors(registry);
    
        //注册beanProcessor,比如ReferenceAnnotationBeanPostProcessor
        registerCommonBeans(registry);
        //调用DubboBeanDefinitionParser的parse方法进行解析
        BeanDefinition beanDefinition = super.parse(element, parserContext);
        setSource(beanDefinition);
        return beanDefinition;
    
  • DubboBeanDefinitionParser#parse
private static RootBeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) 
         RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClass(beanClass);
        beanDefinition.setLazyInit(false);
        //优先使用id,次优使用name,最后使用interface.如果包含相同的beanName,则通过增加后缀id来区分
        String id = resolveAttribute(element, "id", parserContext);
        if (StringUtils.isEmpty(id) && required) 
            String generatedBeanName = resolveAttribute(element, "name", parserContext);
            if (StringUtils.isEmpty(generatedBeanName)) 
                if (ProtocolConfig.class.equals(beanClass)) 
                    generatedBeanName = "dubbo";
                 else 
                    generatedBeanName = resolveAttribute(element, "interface", parserContext);
                
            
            if (StringUtils.isEmpty(generatedBeanName)) 
                generatedBeanName = beanClass.getName();
            
            id = generatedBeanName;
            int counter = 2;
            while (parserContext.getRegistry().containsBeanDefinition(id)) 
                id = generatedBeanName + (counter++);
            
        
        if (StringUtils.isNotEmpty(id)) 
            if (parserContext.getRegistry().containsBeanDefinition(id)) 
                throw new IllegalStateException("Duplicate spring bean id " + id);
            
            //手动注册
            parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
            beanDefinition.getPropertyValues().addPropertyValue("id", id);
        
        //省略部分非关键代码
      

可以看到通过parserContext.getRegistry().registerBeanDefinition(id, beanDefinition)将自定义schema标签解析成beanDefinition注册到beanDefinitionMap中,解析完如下所示:

三、 总结

        通过自定义命名空间找到自定义NamespaceHandler,通过自定义handler解析自定义标签生成beanDefinition注册到容器中。

以上是关于dubbo源码系列3——dubbo自定义标签解析的主要内容,如果未能解决你的问题,请参考以下文章

dubbo源码学习 : spring 自定义标签

6.2 dubbo在spring中自定义xml标签源码解析

Dubbo源码分析系列-扩展机制的实现

dubbo源码—dubbo自定义spring xml标签

Dubbo源码-Dubbo是如何随心所欲自定义XML标签的

Spring如何解析Dubbo标签