前言
進行Dubbox的代碼分析,版本2.8.4。
從淺入深,從實踐出發,dubbox源碼分析
啟動
pom.xml文件
<parent>
<groupId>com.alibaba</groupId>
<artifactId>dubbo-parent</artifactId>
<version>2.8.4</version>
</parent>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>${project.parent.version}</version>
</dependency>
</dependencies>
啟動文件
/**
* 默認啟動一個dubbo容器
*
* @author linxm
*
*/
public class Step01 {
public static void main(String[] args) {
com.alibaba.dubbo.container.Main.main(args);
}
}
運行的效果如下
log4j:WARN No appenders could be found for logger (com.alibaba.dubbo.common.logger.LoggerFactory).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
[2017-07-22 22:27:49] Dubbo service server started!
服務正常啟動,但是日志有警告,這是因為默認使用log4j作為日志處理器,需要讀取配置文件,在classpath目錄下創建log4j.xml文件
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="false">
<appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="[%d{dd/MM/yy hh:mm:ss:sss z}] %t %5p %c{2}: %m%n" />
</layout>
</appender>
<appender name="dubboAppender" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="c:/temp/dubbo.log" />
<param name="DatePattern" value="'.'yyyy-MM-dd'.log'" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern"
value="[%d{MMdd HH:mm:ss SSS\} %-5p] [%t] %c{3\} - %m%n" />
</layout>
</appender>
<root>
<level value="info" />
<appender-ref ref="CONSOLE" />
<appender-ref ref="dubboAppender" />
</root>
</log4j:configuration>
再次運行應用,將顯示正常的輸出信息!
擴展
這里我們將展示一下如何擴展功能,功能的擴展需要做如下幾個步驟的工作
- 擴展接口需要注解 @SPI
- 加載的時候,默認加載路徑:META-INF/dubbo/internal/;META-INF/services/
- 擴展的定義文件以擴展接口為名字,放置在默認加載路徑下,文件內容key,Value方式存儲擴展接口的實現
下面我們將詳細展示一下如何操作
- 定義一個擴展接口
// 這個命名為默認的接口實現實例名稱
@SPI("default")
public interface ShowMessage {
public String getMessage();
}
- 多個接口實現
public class FirstShowMessage implements ShowMessage{
public String getMessage() {
return "第一個信息";
}
}
@Adaptive
public class SecondMessage implements ShowMessage{
public String getMessage() {
return "這是第二個信息";
}
}
public class DefaultMessage implements ShowMessage{
public String getMessage() {
return "顯示默認信息!";
}
}
- 擴展定義文件
書寫一個文件,名稱與擴展接口完全相同 me.helllp.demo.dubboxStudy.spi.ShowMessage,文件的內容如下
first=me.helllp.demo.dubboxStudy.spi.impl.FirstShowMessage
second=me.helllp.demo.dubboxStudy.spi.impl.SecondMessage
default=me.helllp.demo.dubboxStudy.spi.impl.DefaultMessage
將此文件放置在 META-INF/services/ 目錄下
- 執行應用,查看效果
public class Step02 {
public static void main(String[] args) {
ExtensionLoader<ShowMessage> loader = ExtensionLoader.getExtensionLoader(ShowMessage.class);
// 通過指定名稱獲取實例
System.out.println(loader.getExtension("first").getMessage());
// 獲取默認擴展實例,
System.out.println(loader.getDefaultExtension().getMessage());
// 獲取@Adaptive實例
System.out.println(loader.getAdaptiveExtension().getMessage());
}
}
配置容器
首先我們看一下配置的方法 com.alibaba.dubbo.common.utils.ConfigUtils
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;
}
上面的方法是讀取默認的配置信息,常數的定義
public static final String DUBBO_PROPERTIES_KEY = "dubbo.properties.file";
public static final String DEFAULT_DUBBO_PROPERTIES = "dubbo.properties";
也就是說,系統參數中可以通過 dubbo.properties.file 指定配置文件的名稱;配置文件默認為 dubbo.properties
容器的接口:com.alibaba.dubbo.container.Container
容器的實現:META-INF/dubbo/internal/com.alibaba.dubbo.container.Container 文件中進行了定義
spring=com.alibaba.dubbo.container.spring.SpringContainer
javaconfig=com.alibaba.dubbo.container.javaconfig.JavaConfigContainer
jetty=com.alibaba.dubbo.container.jetty.JettyContainer
log4j=com.alibaba.dubbo.container.log4j.Log4jContainer
logback=com.alibaba.dubbo.container.logback.LogbackContainer
我們可以在dubbo.properties配置文件中設置
# 修改容器
dubbo.container=jetty
增加pom.xml文件中的jetty依賴
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty</artifactId>
</dependency>
運行容器啟動之后,可以看到輸出信息中明確了使用Jetty作為dubbox容器
[22/07/17 10:42:05:005 CST] main INFO logger.LoggerFactory: using logger: com.alibaba.dubbo.common.logger.log4j.Log4jLoggerAdapter
[22/07/17 10:42:05:005 CST] main INFO container.Main: [DUBBO] Use container type([jetty]) to run dubbo serivce., dubbo version: 2.8.4, current host: 127.0.0.1
如何集成進spring
在dubbox中,依賴spring進行bean的管理。那么,dubbox是如何與spring集成到一起的那。我們看一下dubbox spring容器的代碼
下面是 com.alibaba.dubbo.container.spring.SpringContainer 最主要的一部分
public static final String SPRING_CONFIG = "dubbo.spring.config";
public static final String DEFAULT_SPRING_CONFIG = "classpath*:META-INF/spring/*.xml";
static ClassPathXmlApplicationContext context;
public static ClassPathXmlApplicationContext getContext() {
return context;
}
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();
}
從上面的代碼中我們可以得到如下幾個結論:
- 可以在dubbo.properties中通過key=dubbo.spring.config來設置spring配置文件的位置
- 默認的spring配置文件的位置:classpath:META-INF/spring/.xml**
所以我們寫一個最簡單的配置文件 dubbox-first-demo.xml,將文件放置在 META-INF/spring/ 目錄下
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="test-services" owner="linxm" organization="workhouse"/>
</beans>
這個時候運行服務,得到如下的輸出,顯示已經加載了我們自己定義的配置文件
[22/07/17 10:56:32:032 CST] main INFO support.ClassPathXmlApplicationContext: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@3d04a311: startup date [Sat Jul 22 22:56:32 CST 2017]; root of context hierarchy
[22/07/17 10:56:32:032 CST] main INFO xml.XmlBeanDefinitionReader: Loading XML bean definitions from file [C:\eclipse-luna-x86_64\workspace\dubboxStudy\target\classes\META-INF\spring\dubbox-first-demo.xml]
[22/07/17 10:56:32:032 CST] main INFO support.DefaultListableBeanFactory: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@6adede5: defining beans [test-services]; root of factory hierarchy
源碼分析:我們可以在dubbo-2.8.4.jar!META-INFO下找到下面幾個關鍵的文件
dubbo.xsd
spring.handlers
spring.schemas
dubbox通過自定義XSD標簽來進行配置,要了解這部分內容,需要先了解spring自定義XSD的處理邏輯
spring自定義標簽
spring支持通過XSD進行自定義標簽的擴展,具體的流程如下
- 定義bean
- 定義XSD文件,通常放置在META-INF目錄下
- META-INF/spring.handlers spring容器默認在這里讀取XSD解析類
- META-INF/spring.schemas spring容器默認在這里讀取XSD配置信息
- 編寫XSD解析類 NamespaceHandler
- 編寫Bean解析類 BeanDefinitionParser
為了更好的理解這部分邏輯,下面我們在上面工程的基礎上書寫一個演示用的例子
- 定義bean:me.helllp.demo.dubboxStudy.schema.DemoBean
private String id;
private String name;
private Integer age;
- 書寫XSD文件:META-INF\demo.xsd
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://me.helllp.dubbox/schema/demo"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:tool="http://www.springframework.org/schema/tool"
targetNamespace="http://me.helllp.dubbox/schema/demo">
<xsd:import namespace="http://www.springframework.org/schema/beans" />
<xsd:element name="info">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="age" type="xsd:int" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:schema>
- 書寫配置文件
# META-INF/spring.handlers
http\://me.helllp.dubbox/schema/demo=me.helllp.demo.dubboxStudy.schema.DemoNamespaceHandler
# META-INF/spring.schemas
http\://me.helllp.dubbox/schema/demo/demo.xsd=META-INF/demo.xsd
- XSD解析類:me.helllp.demo.dubboxStudy.schema.DemoNamespaceHandler
public class DemoNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
System.out.println("===============這是解析XSD的類=================");
registerBeanDefinitionParser("info", new DemoBeanDefinitionParser());
}
}
- Bean解析類:
public class DemoBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
@Override
protected Class<?> getBeanClass(Element element) {
return DemoBean.class;
}
@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
String name = element.getAttribute("name");
String age = element.getAttribute("age");
String id = element.getAttribute("id");
if (StringUtils.hasText(id)) {
builder.addPropertyValue("id", id);
}
if (StringUtils.hasText(name)) {
builder.addPropertyValue("name", name);
}
if (StringUtils.hasText(age)) {
builder.addPropertyValue("age", Integer.valueOf(age));
}
}
}
- 增加一個配置文件:META-INF\spring\schema-demo.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:demo="http://me.helllp.dubbox/schema/demo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://me.helllp.dubbox/schema/demo http://me.helllp.dubbox/schema/demo/demo.xsd">
<demo:info id="code" name="lxm" age="33"></demo:info>
</beans>
- 運行 com.alibaba.dubbo.container.Main.main(args),我們可以看一下效果!
基于上面的演示用例子,可以讓我們很好的理解dubbox如何自定義標簽,如何解析
解析類:com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
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));
}
}
到這來,剩下的可以自己看了!