github新增倉庫 "dubbo-read"(點此查看),集合所有《Dubbo原理和源碼解析》系列文章,后續將繼續補充該系列,同時將針對Dubbo所做的功能擴展也進行分享。不定期更新,歡迎Follow。
一、Dubbo 配置方式
Dubbo 支持多種配置方式:
- XML 配置:基於 Spring 的 Schema 和 XML 擴展機制實現
- 屬性配置:加載 classpath 根目錄下的 dubbo.properties
- API 配置:通過硬編碼方式配置(不推薦使用)
- 注解配置:通過注解方式配置(Dubbo-2.5.7及以上版本支持,不推薦使用)
對於 屬性配置 方式,可以通過環境變量、-D 啟動參數來指定 dubbo.properties 文件,加載文件順序為:
-
- -D 啟動參數
- 環境變量
- classpath 根目錄
加載代碼如下:
public static final String DUBBO_PROPERTIES_KEY = "dubbo.properties.file"; public static final String DEFAULT_DUBBO_PROPERTIES = "dubbo.properties"; private static volatile Properties PROPERTIES; public static Properties getProperties() { if (PROPERTIES == null) { synchronized (ConfigUtils.class) { if (PROPERTIES == null) { String path = System.getProperty(Constants.DUBBO_PROPERTIES_KEY); if (path == null || path.length() == 0) { path = System.getenv(Constants.DUBBO_PROPERTIES_KEY); if (path == null || path.length() == 0) { path = Constants.DEFAULT_DUBBO_PROPERTIES; } } PROPERTIES = ConfigUtils.loadProperties(path, false, true); } } } return PROPERTIES; }
本文主要分析 XML 配置的實現原理和源碼,其他方式不予贅述。
二、XML 配置
文章開頭已經提到,XML 配置方式是基於 Spring 的 Schema 和 XML 擴展機制實現的。通過該機制,我們可以編寫自己的 Schema,並根據自定義的 Schema 自定義標簽來配置 Bean。
使用 Spring 的 XML 擴展機制有以下幾個步驟:
-
- 定義 Schema(編寫 .xsd 文件)
- 定義 JavaBean
- 編寫 NamespaceHandler 和 BeanDefinitionParser 完成 Schema 解析
- 編寫 spring.handlers 和 spring.schemas 文件串聯解析部件
- 在 XML 文件中應用配置
Dubbo 配置相關的代碼在 dubbo-config 模塊。
2.1 定義 Schema
Schema 的定義體現在 .xsd 文件上,文件位於 dubbo-config-spring 子模塊下:
至於 XSD 的數據類型、如何定義,並不是本文的重點,請參考 W3school《Schema 教程》。
2.2 定義 JavaBean
dubbo-config-api 子模塊中定義了 Dubbo 所有標簽對應的 JavaBean,JavaBean 里面的屬性一一對應標簽的各配置項。
2.3 解析 Schema
Dubbo 服務框架的 Schema 的解析通過 DubboNamespaceHandler 和 DubboBeanDefinitionParser 實現。
其中,DubboNamespaceHandler 擴展了 Spring 的 NamespaceHandlerSupport,通過重寫它的 init() 方法給各個標簽注冊對應的解析器:
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)); registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true)); registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false)); registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true)); } }
而 DubboBeanDefinitionParser 實現了 Spring 的 BeanDefinitionParser,通過重寫 parse() 方法實現將標簽解析為對應的 JavaBean:
public class DubboBeanDefinitionParser implements BeanDefinitionParser { public BeanDefinition parse(Element element, ParserContext parserContext) { return parse(element, parserContext, beanClass, required); } @SuppressWarnings("unchecked") private static BeanDefinition parse(Element element,ParserContext parserContext,Class<?> beanClass,boolean required) { RootBeanDefinition beanDefinition = new RootBeanDefinition(); beanDefinition.setBeanClass(beanClass); beanDefinition.setLazyInit(false); //......省略 if (ProtocolConfig.class.equals(beanClass)) { for (String name : parserContext.getRegistry().getBeanDefinitionNames()) { BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(name); PropertyValue property = definition.getPropertyValues().getPropertyValue("protocol"); if (property != null) { Object value = property.getValue(); if (value instanceof ProtocolConfig && id.equals(((ProtocolConfig) value).getName())) { definition.getPropertyValues().addPropertyValue("protocol", new RuntimeBeanReference(id)); } } } } else if (ServiceBean.class.equals(beanClass)) { String className = element.getAttribute("class"); if(className != null && className.length() > 0) { RootBeanDefinition classDefinition = new RootBeanDefinition(); classDefinition.setBeanClass(ReflectUtils.forName(className)); classDefinition.setLazyInit(false); parseProperties(element.getChildNodes(), classDefinition); beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl")); } } else if (ProviderConfig.class.equals(beanClass)) { parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition); } else if (ConsumerConfig.class.equals(beanClass)) { parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition); } //......省略 return beanDefinition; } }
2.4 串聯部件
上面我們已經知道解析的實現類了,那么 Spring 又如何知道該用 DubboNamespaceHandler 來解析 Dubbo 標簽呢?這通過編寫 spring.handlers 文件實現。
spring.handlers 內容如下:
http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
然后,Spring 通過 spring.schemas 文件得知 Dubbo 標簽的 Schema 是 dubbo.xsd,並以此校驗應用 XML 配置文件的格式。
spring.schemas 文件內容如下:
http\://code.alibabatech.com/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd
文件位置如下:
2.5 應用配置
通過以上步驟,Dubbo 服務框架就完成了標簽解析的功能,用戶在應用程序中按照 dubbo.xsd 的格式配置 XML 即可。
Over.