解析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的值可以使用,; \t\n(逗號|分號|空格|制表符|換行)分隔開(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

    ---恢復內容結束---


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM