本文只讨论dubbo是这么启动的,以及粗略探讨dubbo启动的类层次结构图,dubbo的配置细节功能请参考dubbo官方文档,不做详细解释

 

1.dubbo是怎么启动的

  Dubbo是这么启动的,通过了解dubbo官方文档和阅读dubbo源码可以发现,dubbo启动的方式有两种,一种是借助spring启动一种是直接new对象启动

 

首先我们看看dubbo通过spring方式进行启动,dubbo有一个container模块,负责管理dubbo加载启动,日志管理这个模块一共分为5个子模块:dubbo-container-api,dubbo-container-jetty,dubbo-container-log4j,dubbo-container-logback,dubbo-container-spring

dubbo项目部署docker dubbo container_java

dubbo-container-log4j

自动配置log4j的配置,在多进程启动时,自动给日志文件按进程分目录,如果启动的时候启动这个模块,用户不能控制log4j的配置,不灵活

dubbo-container-logback

dubbo-container-jetty

启动一个内嵌Jetty,用于汇报状态,大量访问页面时,会影响服务器的线程和内存

dubbo-container-spring

  自动加载META-INF/spring目录下的所有Spring配置,这个是我个人认为比较推荐的启动dubbo的方式,dubbo通过SPI的机制,默认的会开启spring这个容器的加载dubbo配置,完成初始化操作。

 

@SPI("spring")
public interface Container {
    
    /**
     * start.
     */
    void start();
    
    /**
     * stop.
     */
    void stop();

}

从Container这个接口可以看出spring作为默认的dubbo加载器,dubbo为我们提供了一个模块用来启动dubbo服务的,这个类的名称是Main位于dubbo-container-api包里,dubbo为用户提供服务的模块往往以api为结尾,我们可以看一下Main这个类里到底都做了什么

public class Main {

    public static final String CONTAINER_KEY = "dubbo.container";

    public static final String SHUTDOWN_HOOK_KEY = "dubbo.shutdown.hook";
    
    private static final Logger logger = LoggerFactory.getLogger(Main.class);

    private static final ExtensionLoader<Container> loader = ExtensionLoader.getExtensionLoader(Container.class);
    
    private static volatile boolean running = true;

    public static void main(String[] args) {
        try {
            if (args == null || args.length == 0) {
                // 如果启动不传递参数获取默认配置
                String config = ConfigUtils.getProperty(CONTAINER_KEY, loader.getDefaultExtensionName());
                args = Constants.COMMA_SPLIT_PATTERN.split(config);
            }
            
            // 取到所有的启动要初始化的所有工作主要指log4j,logbac,jetty,spring
            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.");
            
             // 如果容器里已经启动过了就停止,优雅的停止java线程,俗称钩子,这里放掉主线程停止程序
            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) {
                }
            }
        }
    }
    
}

这里的容器链就是指dubbo-container这个项目下的dubbo-container-jetty,dubbo-container-log4j,dubbo-container-logback,dubbo-container-spring,通过启动Main类的main方法的参数设置

public class DubboMain {
    private static final Logger logger = LoggerFactory.getLogger(DubboMain.class);

    public static void main(String[] args){
        com.alibaba.dubbo.container.Main.main(new String[]{"spring","log4j"});
    }
}

通过main方法传递参数必须在META-INF.dubbo.internal文件夹配置,这样spring在启动的时候才能加载的到

dubbo项目部署docker dubbo container_spring_02

Dubbo容器的log4j,logback,jetty这里不细描述,我们主要看看dubbo的container的spring模块代码

public class SpringContainer implements Container {

    private static final Logger logger = LoggerFactory.getLogger(SpringContainer.class);

    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();
    }

    public void stop() {
        try {
            if (context != null) {
                context.stop();
                context.close();
                context = null;
            }
        } catch (Throwable e) {
            logger.error(e.getMessage(), e);
        }
    }

}

Spring模块的代码也就是用spring加载dubbo,dubbo有一个默认的方式spring配置文件的地方,默认启动Main类的main方法可以默认加载这个文件夹下所有的xml文件,然而问题来了,spring是这么加载dubbo了,这里就是dubbo的精髓了,dubbo的扩展机制

Dubbo定义了DubboBeanDefinitionParser类用来解析dubbo自定义的spring标签,自定义标签要继承spring的BeanDefinitionParser实现parse方法解析自定标签的参数。

找到dubbo-config\dubbo-config-spring\src\main\resources\META-INF\spring.handlers文件。找到负责具体解析dubbo标签的handler。

http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler

DubboNamespaceHandler类将Dubbo自定义标签解析的bean注册到spring中

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));
    }

从代码中可以看到dubbo自定义标签元素,也可以知道dubbo就是这时候初始化的,那么问题来了是哪个类加载的时候初始化的了看下图

dubbo项目部署docker dubbo container_runtime_03

 

Dubbo服务端是在ServiceBean中初始化的,dubbo的客户端是在ReferenceBean中初始化的。

本文只讨论dubbo是这么启动的,以及粗略探讨dubbo启动的类层次结构图,dubbo的配置细节功能请参考dubbo官方文档,不做详细解释

 

1.dubbo是怎么启动的

  Dubbo是这么启动的,通过了解dubbo官方文档和阅读dubbo源码可以发现,dubbo启动的方式有两种,一种是借助spring启动一种是直接new对象启动

 

首先我们看看dubbo通过spring方式进行启动,dubbo有一个container模块,负责管理dubbo加载启动,日志管理这个模块一共分为5个子模块:dubbo-container-api,dubbo-container-jetty,dubbo-container-log4j,dubbo-container-logback,dubbo-container-spring

dubbo项目部署docker dubbo container_初始化_04

 

dubbo-container-log4j

自动配置log4j的配置,在多进程启动时,自动给日志文件按进程分目录,如果启动的时候启动这个模块,用户不能控制log4j的配置,不灵活

dubbo-container-logback

dubbo-container-jetty

启动一个内嵌Jetty,用于汇报状态,大量访问页面时,会影响服务器的线程和内存

dubbo-container-spring

  自动加载META-INF/spring目录下的所有Spring配置,这个是我个人认为比较推荐的启动dubbo的方式,dubbo通过SPI的机制,默认的会开启spring这个容器的加载dubbo配置,完成初始化操作。

@SPI("spring")
public interface Container {
    
    /**
     * start.
     */
    void start();
    
    /**
     * stop.
     */
    void stop();

}

从Container这个接口可以看出spring作为默认的dubbo加载器,dubbo为我们提供了一个模块用来启动dubbo服务的,这个类的名称是Main位于dubbo-container-api包里,dubbo为用户提供服务的模块往往以api为结尾,我们可以看一下Main这个类里到底都做了什么

public class Main {

    public static final String CONTAINER_KEY = "dubbo.container";

    public static final String SHUTDOWN_HOOK_KEY = "dubbo.shutdown.hook";
    
    private static final Logger logger = LoggerFactory.getLogger(Main.class);

    private static final ExtensionLoader<Container> loader = ExtensionLoader.getExtensionLoader(Container.class);
    
    private static volatile boolean running = true;

    public static void main(String[] args) {
        try {
            if (args == null || args.length == 0) {
                // 如果启动不传递参数获取默认配置
                String config = ConfigUtils.getProperty(CONTAINER_KEY, loader.getDefaultExtensionName());
                args = Constants.COMMA_SPLIT_PATTERN.split(config);
            }
            
            // 取到所有的启动要初始化的所有工作主要指log4j,logbac,jetty,spring
            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.");
            
             // 如果容器里已经启动过了就停止,优雅的停止java线程,俗称钩子,这里放掉主线程停止程序
            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) {
                }
            }
        }
    }
    
}

这里的容器链就是指dubbo-container这个项目下的dubbo-container-jetty,dubbo-container-log4j,dubbo-container-logback,dubbo-container-spring,通过启动Main类的main方法的参数设置

public class DubboMain {
    private static final Logger logger = LoggerFactory.getLogger(DubboMain.class);

    public static void main(String[] args){
        com.alibaba.dubbo.container.Main.main(new String[]{"spring","log4j"});
    }
}

通过main方法传递参数必须在META-INF.dubbo.internal文件夹配置,这样spring在启动的时候才能加载的到

dubbo项目部署docker dubbo container_runtime_05

 

Dubbo容器的log4j,logback,jetty这里不细描述,我们主要看看dubbo的container的spring模块代码

public class SpringContainer implements Container {

    private static final Logger logger = LoggerFactory.getLogger(SpringContainer.class);

    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();
    }

    public void stop() {
        try {
            if (context != null) {
                context.stop();
                context.close();
                context = null;
            }
        } catch (Throwable e) {
            logger.error(e.getMessage(), e);
        }
    }

}

 

Spring模块的代码也就是用spring加载dubbo,dubbo有一个默认的方式spring配置文件的地方,默认启动Main类的main方法可以默认加载这个文件夹下所有的xml文件,然而问题来了,spring是这么加载dubbo了,这里就是dubbo的精髓了,dubbo的扩展机制

Dubbo定义了DubboBeanDefinitionParser类用来解析dubbo自定义的spring标签,自定义标签要继承spring的BeanDefinitionParser实现parse方法解析自定标签的参数。

找到dubbo-config\dubbo-config-spring\src\main\resources\META-INF\spring.handlers文件。找到负责具体解析dubbo标签的handler。

http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler

DubboNamespaceHandler类将Dubbo自定义标签解析的bean注册到spring中,

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));
    }

从代码中可以看到dubbo自定义标签元素,也可以知道dubbo就是这时候初始化的,那么问题来了是哪个类加载的时候初始化的了看下图

dubbo项目部署docker dubbo container_runtime_06

 

Dubbo客户端是在ServiceBean中初始化的,dubbo的客户端是在ReferenceBean中初始化的,下图是ServiceBean的类图层次结构

dubbo项目部署docker dubbo container_初始化_07

 

 

可以看出ServiceBean实现了

InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware 这些接口其中InitializingBean,ApplicationListener两个接口非常的重要,ServiceBean实现了InitializingBean afterPropertiesSet方法在ServiceBean被初始化的时候加载启动dubbo服务端,有就是dubbo服务启动的入口,另外ServiceBean还实现了ApplicationListener的onApplicationEvent在spring refresh的时候触发ContextRefreshedEvent事件加载启动dubbo服务,ReferenceBean实现原理和ServiceBean类似,ReferenceBean的类层次结构图如下:

dubbo项目部署docker dubbo container_dubbo项目部署docker_08

 

 

Dubbo客户端没有实现ApplicationListener接口因此在spring refresh的时候不会重现加载

下面给大家介绍下通过最原始的方式启动spring

通过new的方式启动dubbo服务端

public class ProviderStart {
    public static void main(String[] args) {
        // 当前应用配置
        ApplicationConfig application = new ApplicationConfig();
        application.setName("xxx");

       // 连接注册中心配置
        RegistryConfig registry = new RegistryConfig();
        registry.setAddress("10.20.130.230:9090");
        registry.setUsername("aaa");
        registry.setPassword("bbb");

        // 服务提供者协议配置
        ProtocolConfig protocol = new ProtocolConfig();
        protocol.setName("dubbo");
        protocol.setPort(12345);
        protocol.setThreads(200);

       // 注意:ServiceConfig为重对象,内部封装了与注册中心的连接,以及开启服务端口

       // 服务提供者暴露服务配置
        ServiceConfig<XxxService> service = new ServiceConfig<XxxService>(); // 此实例很重,封装了与注册中心的连接,请自行缓存,否则可能造成内存和连接泄漏
        service.setApplication(application);
        service.setRegistry(registry); // 多个注册中心可以用setRegistries()
        service.setProtocol(protocol); // 多个协议可以用setProtocols()
        service.setInterface(XxxService.class);
        XxxServiceImpl xxxService = new XxxServiceImpl();
        service.setRef(xxxService);
        service.setVersion("1.0.0");

        // 暴露及注册服务
        service.export();
    }

通过new的方式启动dubbo客户端

import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ReferenceConfig;
import com.alibaba.dubbo.config.RegistryConfig;

/**
 * Created by xuehan on 2017/2/23.
 */
public class ConsumerStart {
    public static void main(String[] args) {
        // 当前应用配置
        ApplicationConfig application = new ApplicationConfig();
        application.setName("yyy");

        // 连接注册中心配置
        RegistryConfig registry = new RegistryConfig();
        registry.setAddress("10.20.130.230:9090");
        registry.setUsername("aaa");
        registry.setPassword("bbb");

       // 注意:ReferenceConfig为重对象,内部封装了与注册中心的连接,以及与服务提供方的连接

        // 引用远程服务
        ReferenceConfig<XxxService> reference = new ReferenceConfig<XxxService>();
        // 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏
        reference.setApplication(application);
        reference.setRegistry(registry); // 多个注册中心可以用setRegistries()
        reference.setInterface(XxxService.class);
        reference.setVersion("1.0.0");

         // 和本地bean一样使用xxxService
        XxxService xxxService = reference.get(); // 注意:此代理对象内部封装了所有通讯细节,对象较重,请缓存复用
    }
}