Dubbox的源码学习(一)

作者: hutou | 来源:发表于2017-07-25 23:38 被阅读264次

    前言

    进行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>
    

    再次运行应用,将显示正常的输出信息!

    扩展

    这里我们将展示一下如何扩展功能,功能的扩展需要做如下几个步骤的工作

    1. 扩展接口需要注解 @SPI
    2. 加载的时候,默认加载路径:META-INF/dubbo/internal/;META-INF/services/
    3. 扩展的定义文件以扩展接口为名字,放置在默认加载路径下,文件内容key,Value方式存储扩展接口的实现

    下面我们将详细展示一下如何操作

    1. 定义一个扩展接口
    //  这个命名为默认的接口实现实例名称
    @SPI("default")
    public interface ShowMessage {
        public String getMessage();
    }
    
    1. 多个接口实现
    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 "显示默认信息!";
        }
    }
    
    1. 扩展定义文件
      书写一个文件,名称与扩展接口完全相同 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/ 目录下

    1. 执行应用,查看效果
    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();
        }
    

    从上面的代码中我们可以得到如下几个结论:

    1. 可以在dubbo.properties中通过key=dubbo.spring.config来设置spring配置文件的位置
    2. 默认的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进行自定义标签的扩展,具体的流程如下

    1. 定义bean
    2. 定义XSD文件,通常放置在META-INF目录下
    3. META-INF/spring.handlers spring容器默认在这里读取XSD解析类
    4. META-INF/spring.schemas spring容器默认在这里读取XSD配置信息
    5. 编写XSD解析类 NamespaceHandler
    6. 编写Bean解析类 BeanDefinitionParser

    为了更好的理解这部分逻辑,下面我们在上面工程的基础上书写一个演示用的例子

    1. 定义bean:me.helllp.demo.dubboxStudy.schema.DemoBean
        private String id;  
        
        private String name;
        
        private Integer age;
    
    1. 书写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>   
    
    1. 书写配置文件
    #  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
    
    1. XSD解析类:me.helllp.demo.dubboxStudy.schema.DemoNamespaceHandler
    public class DemoNamespaceHandler extends NamespaceHandlerSupport {
        @Override
        public void init() {
            System.out.println("===============这是解析XSD的类=================");
            registerBeanDefinitionParser("info", new DemoBeanDefinitionParser());
        }
    }
    
    1. 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));  
            } 
        }
    }
    
    1. 增加一个配置文件: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>
    
    1. 运行 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));
        }
    
    }
    

    到这来,剩下的可以自己看了!

    相关文章

      网友评论

        本文标题:Dubbox的源码学习(一)

        本文链接:https://www.haomeiwen.com/subject/cffukxtx.html