一、准備工作
1、下載dubbo源碼,地址:https://github.com/alibaba/dubbo;並將源碼導入eclipse
2、啟動zookeeper注冊中心服務;
3、通過debug的方式運行dubbo-demo,跟進分析源碼;如下圖運行DemoProvider.java中的main方法

二、源碼分析
DemoProvider.java調用com.alibaba.dubbo.container.Main.main(args)方法;代碼如下:
public static void main(String[] args) { try {
//args為空 if (args == null || args.length == 0) {
//讀取dubbo.properties中的dubbo.container的屬性值,為空時通過loader.getDefaultExtensionName()獲取默認值 String config = ConfigUtils.getProperty(CONTAINER_KEY, loader.getDefaultExtensionName()); args = Constants.COMMA_SPLIT_PATTERN.split(config); } final List<Container> containers = new ArrayList<Container>();
//遍歷,獲取指定名稱的擴展加入到列表中 for (int i = 0; i < args.length; i ++) { containers.add(loader.getExtension(args[i])); } logger.info("Use container type(" + Arrays.toString(args) + ") to run dubbo serivce."); //添加jvm關閉的鈎子,用來在jvm關閉時關閉容器 if ("true".equals(System.getProperty(SHUTDOWN_HOOK_KEY))) { Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { for (Container container : containers) { try { container.stop(); logger.info("Dubbo " + container.getClass().getSimpleName() + " stopped!"); } catch (Throwable t) { logger.error(t.getMessage(), t); } synchronized (Main.class) { running = false; Main.class.notify(); } } } }); } //遍歷列表,啟動容器 for (Container container : containers) { container.start(); logger.info("Dubbo " + container.getClass().getSimpleName() + " started!"); } System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " Dubbo service server started!"); } catch (RuntimeException e) { e.printStackTrace(); logger.error(e.getMessage(), e); System.exit(1); } synchronized (Main.class) { while (running) { try { Main.class.wait(); } catch (Throwable e) { } } } }
示例中啟動了兩個容器,分別是:Log4jContainer,SpringContainer。Log4jContainer容器不用理會,主要是對日志初始化;重點在SpringContainer容器,其start()方法源碼如下:
public void start() { String configPath = ConfigUtils.getProperty(SPRING_CONFIG); if (configPath == null || configPath.length() == 0) { configPath = DEFAULT_SPRING_CONFIG; }
//解析配置文件,刷新應用上下文 context = new ClassPathXmlApplicationContext(configPath.split("[,\\s]+")); context.start(); }
查看ClassPathXmlApplicationContext源碼會發現其調用了refresh()方法,該方法會對給定路徑的文件進行解析裝配,這里涉及到spring解析xml相關知識。Spring在解析xml文件時遇到dubbo名稱空間時,會回調DubboNamespaceHandler,對所有dubbo的標簽,都統一用DubboBeanDefinitionParser進行解析,基於一對一屬性映射,將XML標簽解析為Bean對象。如下圖:spring.handlers文件指定了Dubbo命名空間處理器,spring.schemas文件配置了路徑映射;DubboNamespaceHandler.java,DubboBeanDefinitionParser.java為Dubbo對spring的自定義的擴展。

官方文檔給出的解析服務大致過程,如下:
- 基於dubbo.jar內的META-INF/spring.handlers配置,Spring在遇到dubbo名稱空間時,會回調DubboNamespaceHandler。
- 所有dubbo的標簽,都統一用DubboBeanDefinitionParser進行解析,基於一對一屬性映射,將XML標簽解析為Bean對象。
- 在ServiceConfig.export()或ReferenceConfig.get()初始化時,將Bean對象轉換URL格式,所有Bean屬性轉成URL的參數。
- 然后將URL傳給Protocol擴展點,基於擴展點的Adaptive機制,根據URL的協議頭,進行不同協議的服務暴露或引用。
那么,ServiceConfig.export()和ReferenceConfig.get()這兩個方法是在何時調用的了?這里要提到兩個很關鍵的類:ServiceBean.java和ReferenceBean.java
public class ServiceBean<T> extends ServiceConfig<T>
implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware{...}
對服務提供方:ServiceBean繼承了ServiceConfig,並且實現了一些列的Spring接口。實現了InitializingBean,所以在bean的創建過程中,由於ServiceBean是單例模式(每個配置就是唯一的一個ServiceBean,包含的interface以及ref都已經固定了),每個接口配置<dubbo:service ...>被解析成ServiceBean實例, 其在初始化后,會調用afterPropertiesSet()方法完成整個dubbo配置的加載過程;實現了ApplicationListener,所以會在整個Spring容器加載完成后接收到消息,完成onApplicationEvent()方法的調用,該方法就會將該ServiceBean配置的接口等服務進行發布和注冊。
public class ReferenceBean<T> extends ReferenceConfig<T>
implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean{...}
同理,對服務消費方:ReferenceBean繼承了ReferenceConfig,原理對應ServiceBean和ServiceConfig。不同之處在於,服務提供方ServiceBean需要在Spring啟動的時候就提供服務,所以是通過onApplicationEvent在容器初始化完成之后立即就發布和注冊了服務;但是服務消費方ReferenceBean是在程序需要的時候才會去執行,如果通過配置在Spring啟動的過程中完成初始化是並不是合適的做法,而是在應用程序需要用到的時候,再去創建,所以ReferenceBean實現了FactoryBean,實現了接口方法getObject(),那么在spring容器getBean()方法獲取對象實例其實調用的是ReferenceBean的getObject()方法完成消費地址的注冊以及服務的訂閱;返回的是
// 接口代理類引用
private transient volatile T ref;
而這個引用對於每個<dubbo:reference>也是唯一的,因為會緩存起來,所以雖然ReferenceBean看上去是工廠模式,實際上返回的都是同一個引用,所以模擬成工廠bean主要是為了在應用程序需要使用的時候才去創建對象,畢竟Proxy創建的成本還是比較大的,這樣做也能很大程序提高程序的效率。
至此整個Spring容器啟動之后服務提供者、服務消費者相關初始化工作基本完成。
