• 解析spring启动加载dubbo过程


    一:简单配置

    web.xml

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    

    applicationContext.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
           http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
        <!-- 扫描注解 -->
        <context:component-scan base-package="per.qiao" />
        <!-- 目的:找到注册中心,注册当前服务的基本信息 -->
    
        <!-- 1.配置别名, 目的是在后台可以看到这个服务的别名,好区分到底是谁 -->
        <dubbo:application name="serviceImpl"/>
        <!-- 2. 配置注册中心  address : 注册中心地址 protocol: 注册中心的协议格式 -->
        <dubbo:registry address="192.168.199.247:2181" protocol="zookeeper" />
        <!-- 3. 选择需要暴露的方法 interface: 目标类的类型 ref: 目标类的具体实现 timeout: 超时连接时间-->
        <dubbo:service interface="per.qiao.service.TestService" ref="serviceImpl" timeout="10000" />
        <!-- 配置当前服务暴露的端口 以及暴露协议 -->
        <dubbo:protocol name="dubbo" port="9000"/>
    
    </beans>
    

    dubbo的默认文件

    spring.handlers文件
    http://dubbo.apache.org/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
    http://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
    
    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
    

    说明: spring.handlers文件用来配置解析dubbo标签并封装成对应的对象

    ​ **spring.schemas文件用来配置schame文件的位置 **

    当spring容器扫描到配置文件,比如applicationContext时,遇到名称空间xmlns:dubbo="http://code.alibabatech.com/schema/dubbo",就会通过名称空间去查询对应的xsd约束文件,就如schemaLocation中配置的

    • xsi:schemaLocation=http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd
    • 再用这个找到的xsd去spring.schemas文件中找到xsd文件的位置,并校验xsd文件的正确性,返回当前文件(applicationContext.xml)的Document对象

    二、启动过程

    加载dubbo属性时序图

    1. 由ContextLoaderListener进入,调用contextInitialized进入initWebApplicationContext开始spring容器

    2. 进入configureAndRefreshWebApplicationContext方法, 然后调用refresh方法

    3. refresh中有个obtainFreshBeanFactory方法,进去

    4. 走流水线AbstractApplicationContext->AbstractRefreshableApplicationContext#refreshBeanFactory -> XmlWebApplicationContext#loadBeanDefinitions

    5. 进入到XmlBeanDefinitionReader#loadBeanDefinitions(configLocation)

      // 遍历你contextConfigLocation配置的多个xml文件
      for (String configLocation : configLocations) {
      	reader.loadBeanDefinitions(configLocation);
      }
      
    6. 来到AbstractBeanDefinitionReader#loadBeanDefinitions-> XmlBeanDefinitionReader#loadBeanDefinitions, doLoadBeanDefinitions, registerBeanDefinitions

      看一下doLoadBeanDefinitions 这里会加载你的spring.schame文件

    //这个方法会检查你的文件(eg. applicationContext.xml)中的schame的namespace对应的xsd是否正确,返回当前文件的Document对象
    Document doc = doLoadDocument(inputSource, resource);
    return registerBeanDefinitions(doc, resource);
    

    这个方法是 注册给定DOM文档中包含的bean定义, 也就是解析你的applicationContext.xml中定义的bean
    registerBeanDefinitions

    public int registerBeanDefinitions(Document doc, Resource resource) throws... {
    	//创建新的doc解析器
       BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
       // 已有的bean个数
       int countBefore = getRegistry().getBeanDefinitionCount();
       // 读取document中的bean
       documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
       return getRegistry().getBeanDefinitionCount() - countBefore;
    }
    
    1. DefaultBeanDefinitionDocumentReader#registerBeanDefinitions
    @Override
    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    	this.readerContext = readerContext;
    	Element root = doc.getDocumentElement();
    	//读取bean
    	doRegisterBeanDefinitions(root);
    }
    
    1. BeanDefinitionParserDelegate#parseCustomElement 这里就从容器中调用到自定义的handler中
    	public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
          	//获取节点的NamespaceURI, 如果当前节点是dubbo:application 那么就是根据dubbo找到
          //xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" 后面的uri
    		String namespaceUri = getNamespaceURI(ele);
    
          //这里的resolve方法,会加载spring.handlers文件,调用namespaceHandler.init();获取当前节点对应的handler解析器
    		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
          //parse方法会先根据element的名称获取对应的BeanDefinitionParser
          //比如当前元素是dubbo:application, 这里会用application作为key去获取对应的解析器,然后调用其parse方法
    		return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    	}
    
    1. DubboBeanDefinitionParser#parse

    关于第8步,加载了spring.handlers文件,然后调用DubboNamespaceHandler的init方法,然后是registerBeanDefinitionParser方法,该方法将节点名称和解析封装在NamespaceHandlerSupport的map中

    protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
    		this.parsers.put(elementName, parser);
    	}
    

    然后在handler.parse(...) -> NamespaceHandlerSupport#findParserForElement方法里, 能通过下面的方式获取到

    BeanDefinitionParser parser = this.parsers.get(localName);
    

    三、说一个遇到的坑-关于idea的this与toString

    在查看解析spring.schames文件的源码时,走到PluggableSchemaResolver类里面,当断点进入resolveEntity方法时,属性private volatile Map<String, String> schemaMappings; 已经有值了,这是不正常的,为此我花了很长时间去找原因

    ​ 最后发现,是由于idea在debug时,在debugger界面有个this引用着当前对象,它会调用当前类的toString()方法,而且是构造方法执行后每执行一步,它刷新一次(调用toString()),由于toString方法中有调用getSchemaMappings方法,会给你加载数据。 最坑的是,idea调用的用断点拦截不到

    this引起的烦恼

    你可以尝试着还原这个坑

    public class MyTest {
        public static void main(String[] args) {
            BB b = new BB();
        }
        static class BB {
            private int n = 0;
            public BB() {
                System.out.println("i am qiao" + n);
            }
            @Override
            public String toString() {
                n = 2;
                System.out.println("hello");
                return  "n == " + n;
            }
        }
    }
    

    四、小结:

    1. web.xml中 参数contextConfigLocation的值可以使用,; (逗号|分号|空格|制表符|换行)分隔开(ContextLoader.INIT_PARAM_DELIMITERS)

    2. classpath:与classpath*: 的区别

      classpath: 会从classes目录下获取文件

      classpath*: 会从所有路径下加载文件,包括jar包

      详见:PathMatchingResourcePatternResolver#getResources

    3. spring.handlers文件用来配置解析dubbo标签的handler

      spring.schemas文件用来配置schame文件的位置

      PluggableSchemaResolver类解析的spring.shames

      DefaultNamespaceHandlerResolver类解析的spring.handler

      ---恢复内容结束---

  • 相关阅读:
    【引用】Android.mk简介
    android02android的四大组件
    rpm 安装指令全
    android04activity的布局管理器
    代码积累1统计图
    清除防火墙所有配置规则
    代码积累2tab页面滑动效果
    RHEL5 配置YUM源 安装RZSZ
    系统安全漏洞扫描软件
    liunx下防火墙的配置
  • 原文地址:https://www.cnblogs.com/qiaozhuangshi/p/10831332.html
Copyright © 2020-2023  润新知