dubbo源码学习(1)--服务发布

一、demo构建

1. demo项目结构如图所示:
dubbo源码学习(1)--服务发布_第1张图片
demo项目结构.jpg

包含api,consumer,provider三个module
api如下所示,提供provider需要暴露的接口

package com.alibaba.dubbo.demo;

import java.util.List;

/**
 * Created by Xkane on 2018/9/13.
 */
public interface DemoService {
    List getPermissions(Long id);
}
2. provider 发布服务配置文件
dubbo源码学习(1)--服务发布_第2张图片
项目provider配置文件.jpg
3. consumer订阅服务配置文件
dubbo源码学习(1)--服务发布_第3张图片
demo-consumer的配置文件.jpg

二、dubbo框架结构

dubbo框架设计总共分了10层:
1. 服务接口层(Service):
    该层是与实际业务逻辑相关,就如上面demo配置的 
    
    
    这个service就是业务方自己定义的接口与其实现。
2.配置层(Config):
    该层是将业务方的service信息,配置文件的信息进行收集配置,主要是以ServiceConfig和ReferenceConfig为中心,
ServiceConfig是配置提供方provider提供的配置,当Spring启动的时候会响应的启动provider服务发布和注册的过程,
主要是加入一个ServiceBean继承ServiceConfig在spring注册,同理ReferenceConfig是consumer方的配置,
当消费方consumer启动时,会启动consumer的发布服务订阅服务的过程,当然也是使用一个ReferenceBean继承
ReferenceConfig注册在spring上
3. 服务代理层(Proxy):
   对服务接口进行透明代理,生成服务的客户端和服务端,使服务的远程调用就像在本地调用一样,默认使用JavassistProxyFactory,
返回一个Invoker,Invoker则是个可执行核心实体,Invoker的invoke方法通过反射执行service方法。
4. 服务注册层(Registry):
    封装服务地址的注册和发现,以服务URL为中心,基于zk。
5. 集群层(Cluster): 
    封装多个提供者的路由及负载均衡,并桥接注册中心,以Invoker为中心,扩展接口为Cluster、Directory、Router和LoadBalance。
    将多个服务提供方组合为一个服务提供方,实现对服务消费方来透明,只需要与一个服务提供方进行交互。
6. 监控层(Monitor):
    RPC调用次数和调用时间监控,以Statistics为中心,扩展接口为MonitorFactory、Monitor和MonitorService。
7. 远程调用层(Protocol):
    封装RPC调用,provider通过export方法进行暴露服务/consumer通过refer方法调用服务。而Protocol依赖的是Invoker。通过上面说的Proxy获得的Invoker,包装成Exporter。
8.信息交换层(Exchange):
    该层封装了请求响应模型,将同步转为异步,信息交换层依赖Exporter,最终将通过网络传输层接收调用请求RequestFuture和ResponseFuture。
9.网络传输层(Transport):
    抽象mina和netty为统一接口,以Message为中心,扩展接口为Channel、Transporter、Client、Server和Codec。
10.数据序列化层:
    该层无需多言,将数据序列化反序列化。

三、dubbo发布过程

发布过程的一些动作

暴露本地服务
暴露远程服务
启动netty
连接zookeeper
到zookeeper注册
监听zookeeper

流程图:


dubbo源码学习(1)--服务发布_第4张图片
RPC发布流程图(引用).png

dubbo发布服务涉及到的相关类。


dubbo源码学习(1)--服务发布_第5张图片
类图.png

上图展示了部分服务发布过程中需要使用到的类和接口,其中:

spring适配涉及到的类DubboNamespaceHandler、DubboBeanDefinitionParser、ServiceBean;
配置信息存储ServicdConfig、RegistryConfig、MonitorConfig、ProtocolConfig、ProviderConfig等;
应用协议Protocol、DubboProtocol、HessianProtocol、ThriftProtocol、RmiProtocol、AbstractProxyProtocol、AbstractProtocol等;
Server相关Exchanger、HeaderExchanger、ExchangeServer、HeaderExchangeServer、Transporters、Transporter、NettyTransporter、NettyServer等;

  1. 通过demo配置文件查看
    
    

    
    

    
    

    
    

    
    

是通过dubbo的schema service进行注入的,找到DubboNameSpaceHandler,Dubbo命名空间处理器,找到标签解析行:
此时,进入DubboNameSpaceHandler类,初始化配置文件,spring容器通过DubboBeanDefinitionParser类的parse方法来解xml文件中的标签,生成ServiceConfig等配置对象;

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));
         //标签解析行,本例中使用 zookeeper 注册中心暴露服务
        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));
        //标签解析行,例如,demo中用dubbo协议在20880端口暴露服务 
        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));
    }
}

从上述代码中可以看到 registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));会把 标签会被解析成 ServiceBeanServiceBean 实现了 InitializingBean,在类加载完成之后会调用afterPropertiesSet() 方法。在afterPropertiesSet() 方法中,依次解析以下标签信息:







在 Spring 容器初始化的时候会调用 onApplicationEvent方法,即spring的事件机制(event),监听Spring 容器初始化ServiceBean重写了 onApplicationEvent方法,实现了服务暴露的功能。

dubbo源码学习(1)--服务发布_第6张图片
延迟方法.png

dubbo暴露服务有两种情况,一种是设置了延迟暴露(比如delay=”5000”),另外一种是没有设置延迟暴露或者延迟设置为-1(delay=”-1”):

1、设置了延迟暴露: dubbo在Spring实例化bean(initializeBean)的时候会对实现了InitializingBean的类进行回调,回调方法是afterPropertySet(),如果设置了延迟暴露,dubbo在这个方法中进行服务的发布。

2、没有设置延迟或者延迟为-1: dubbo会在Spring实例化完bean之后,在刷新容器最后一步发布ContextRefreshEvent事件的时候,通知实现了ApplicationListener的类进行回调onApplicationEvent,dubbo会在这个方法中发布服务。

dubbo源码学习(1)--服务发布_第7张图片
onApplicationEvent.png

ServiceConfig.export()的流程图如下:

ServiceConfig.export()的流程图.png

<1> export()方法
export()进行服务发布,是发布核心。
由上面代码可知,如果设置了 delay 参数,Dubbo 的处理方式是启动一个守护线程在 sleep 指定时间后再 doExport()
dubbo源码学习(1)--服务发布_第8张图片
export.png

从export代码中可以看到,export先判断是否需要延迟暴露,如果设置了延迟delay!=null,则通过一个后台线程Thread.sleep(delay)延迟调用doExport()方法;反之如果不存爱delay,直接调用doExport()方法。
<2> doExport()

protected synchronized void doExport() {
        if (unexported) {
            throw new IllegalStateException("Already unexported!");
        }
        if (exported) {
            return;
        }
        exported = true;
        if (interfaceName == null || interfaceName.length() == 0) {
            throw new IllegalStateException(" interface not allow null!");
        }
        checkDefault();
        if (provider != null) {
            if (application == null) {
                application = provider.getApplication();
            }
            if (module == null) {
                module = provider.getModule();
            }
            if (registries == null) {
                registries = provider.getRegistries();
            }
            if (monitor == null) {
                monitor = provider.getMonitor();
            }
            if (protocols == null) {
                protocols = provider.getProtocols();
            }
        }
        if (module != null) {
            if (registries == null) {
                registries = module.getRegistries();
            }
            if (monitor == null) {
                monitor = module.getMonitor();
            }
        }
        if (application != null) {
            if (registries == null) {
                registries = application.getRegistries();
            }
            if (monitor == null) {
                monitor = application.getMonitor();
            }
        }
        if (ref instanceof GenericService) {
            interfaceClass = GenericService.class;
            generic = true;
        } else {
            try {
                interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                        .getContextClassLoader());
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            checkInterfaceAndMethods(interfaceClass, methods);
            checkRef();
            generic = false;
        }
        if(local !=null){
            if(local=="true"){
                local=interfaceName+"Local";
            }
            Class localClass;
            try {
                localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            if(!interfaceClass.isAssignableFrom(localClass)){
                throw new IllegalStateException("The local implemention class " + localClass.getName() + " not implement interface " + interfaceName);
            }
        }
        if(stub !=null){
            if(stub=="true"){
                stub=interfaceName+"Stub";
            }
            Class stubClass;
            try {
                stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            if(!interfaceClass.isAssignableFrom(stubClass)){
                throw new IllegalStateException("The stub implemention class " + stubClass.getName() + " not implement interface " + interfaceName);
            }
        }
        checkApplication();
        checkRegistry();
        checkProtocol();
        appendProperties(this);
        checkStubAndMock(interfaceClass);
        if (path == null || path.length() == 0) {
            path = interfaceName;
        }
        doExportUrls();
    }

   doExport方法先执行一系列的检查方法,然后调用doExportUrls方法。检查方法会检测dubbo的配置是否在Spring配置文件中声明,没有的话读取properties文件初始化。

doExportUrls.png

   doExportUrls方法先调用 loadRegistries加载注册信息,组装注册中心url信息,如源码中config.properties中读取链接信息,组装Provider注册链接串; 然后遍历调用 doExportUrlsFor1Protocol方法。 里面有个for循环,代表一个服务可以有多个通信协议,例如tcp、http、dubbo等协议,dubbo支持多种协议,默认使用的是dubbo协议。
  debug图中可以看到, name="dubbo"为dubbo协议。
可以在配置文件中进行配置协议:
  

其他几种协议对比:

1 dubbo协议
   连接个数:单连接 
   连接方式:长连接 
   传输协议:TCP 
   传输方式:NIO异步传输 
   序列化:Hessian二进制序列化 
   适用范围:传入传出参数数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供者,尽量不要用dubbo协议传输大文件或超大字符串。 
   适用场景:常规远程服务方法调用
   注:
   1)、dubbo默认采用dubbo协议,dubbo协议采用单一长连接和NIO异步通讯,适合于小数据量大 并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况 
   2)、他不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低。
   3)、为防止被大量连接撑挂,可在服务提供方限制大接收连接数,以实现服务提供方自我保护 
         
2 rmi协议
   Java标准的远程调用协议。 
   连接个数:多连接 
   连接方式:短连接 
   传输协议:TCP 
   传输方式:同步传输 
   序列化:Java标准二进制序列化 
   适用范围:传入传出参数数据包大小混合,消费者与提供者个数差不多,可传文件。 
   适用场景:常规远程服务方法调用,与原生RMI服务互操作
   1)、RMI协议采用JDK标准的java.rmi.*实现,采用阻塞式短连接和JDK标准序列化方式 

3 hessian协议
   基于Hessian的远程调用协议。 
   连接个数:多连接 
   连接方式:短连接 
   传输协议:HTTP 
   传输方式:同步传输 
   序列化:  表单序列化 
   适用范围:传入传出参数数据包大小混合,提供者比消费者个数多,可用浏览器查看,可用表单或URL传入参数,暂不支持传文件。 
   适用场景:需同时给应用程序和浏览器JS使用的服务。
   约束 
      1)、参数及返回值需实现Serializable接口 
      2)、参数及返回值不能自定义实现List, Map, Number, Date, Calendar等接口,只能用JDK自带的实现,因为hessian会做特殊处理,自定义实现类中的属性值都会丢失。

4 http协议
   基于http表单的远程调用协议。参见:[HTTP协议使用说明] 
   连接个数:多连接 
   连接方式:短连接 
   传输协议:HTTP 
   传输方式:同步传输 
   序列化:表单序列化 
   适用范围:传入传出参数数据包大小混合,提供者比消费者个数多,可用浏览器查看,可用表单或URL传入参数,暂不支持传文件。 
   适用场景:需同时给应用程序和浏览器JS使用的服务。

5 webservice协议
   基于WebService的远程调用协议。 
   连接个数:多连接 
   连接方式:短连接 
   传输协议:HTTP 
   传输方式:同步传输 
   序列化:SOAP文本序列化 
   适用场景:系统集成,跨语言调用

6 thrift协议
  当前 dubbo 支持的 thrift 协议是对 thrift 原生协议的扩展,在原生协议的基础上添加了一些额外的头信息,比如service name,magic number等。使用dubbo thrift协议同样需要使用thrift的idl compiler编译生成相应的java代码,后续版本中会在这方面做一些增强 。
  注意:
     Thrift不支持null值,不能在协议中传null

7 memcached协议
  可以通过脚本或监控中心手工填写表单注册memcached服务的地址。

8 redis协议
  可以通过脚本或监控中心手工填写表单注册redis服务的地址。

  继续debug代码,进入方法doExportUrlsFor1Protocol:
doExportUrlsFor1Protocol()先将Bean属性转换成URL对象,然后根据不同协议将服务已URL形式发布。如果scope配置为none则不暴露,如果服务未配置成remote,则本地暴露exportLocal,如果未配置成local,则远程暴露。
  方法第一部分:

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {
       //获取是哪种协议,如果未配置,默认为dubbo
        String name = protocolConfig.getName();
        if (name == null || name.length() == 0) {
            name = "dubbo";
        }
       //获取host
        String host = protocolConfig.getHost();  //从配置中获取
        if (provider != null && (host == null || host.length() == 0)) {
            host = provider.getHost();
        }
        boolean anyhost = false;
        if (NetUtils.isInvalidLocalHost(host)) {  //为了确保获得的主机有效,还有相应的验证:
            anyhost = true;
            try {
                host = InetAddress.getLocalHost().getHostAddress();//返回本机的ip地址
            } catch (UnknownHostException e) {
                logger.warn(e.getMessage(), e);
            }
            if (NetUtils.isInvalidLocalHost(host)) {
                if (registryURLs != null && registryURLs.size() > 0) {
                    for (URL registryURL : registryURLs) {
                        try {
                            Socket socket = new Socket();
                            try {
                                 //通过获取注册中心地址和端口号,创建socket连接地址
                                SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
                                socket.connect(addr, 1000);
                                host = socket.getLocalAddress().getHostAddress();
                                break;
                            } finally {
                                try {
                                    socket.close();
                                } catch (Throwable e) {}
                            }
                        } catch (Exception e) {
                            logger.warn(e.getMessage(), e);
                        }
                    }
                }
                if (NetUtils.isInvalidLocalHost(host)) {
                    host = NetUtils.getLocalHost();//遍历本地网卡,返回第一个合理的IP
                }
            }
        }
       //获取各个协义需要暴露的端口, 按照这样的优先级去获取  :   
  //Protocol的实现类的默认端口 ——> Protocol的配置端口 ——> 随机端口
        Integer port = protocolConfig.getPort();
        if (provider != null && (port == null || port == 0)) {
            port = provider.getPort();
        }
        final int defaultPort = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name).getDefaultPort();
        if (port == null || port == 0) {
            port = defaultPort;
        }
        if (port == null || port <= 0) {
            port = getRandomPort(name);
            if (port == null || port < 0) {
                port = NetUtils.getAvailablePort(defaultPort);
                putRandomPort(name, port);
            }
            logger.warn("Use random available port(" + port + ") for protocol " + name);
        }

  第二部分:收集各类参数,放入map中,在为服务暴露做参数收集准备工作

        // paramsMap,存放所有配置参数,下面生成url用。
        Map map = new HashMap();
        if (anyhost) {
            map.put(Constants.ANYHOST_KEY, "true");
        }
        map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
        map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
        map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
        if (ConfigUtils.getPid() > 0) {
            map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
        }
        appendParameters(map, application);
        appendParameters(map, module);
        appendParameters(map, provider, Constants.DEFAULT_KEY);
        appendParameters(map, protocolConfig);
        appendParameters(map, this);
        // method子标签配置规则解析,retry次数,参数等。没有使用过,不做解释
        if (methods != null && methods.size() > 0) {//服务接口的方法
            for (MethodConfig method : methods) {
                appendParameters(map, method, method.getName());
                String retryKey = method.getName() + ".retry";  //重试次数
                if (map.containsKey(retryKey)) {
                    String retryValue = map.remove(retryKey);
                    if ("false".equals(retryValue)) {
                        map.put(method.getName() + ".retries", "0");
                    }
                }
                List arguments = method.getArguments();
                if (arguments != null && arguments.size() > 0) {
                    for (ArgumentConfig argument : arguments) {
                        //类型自动转换.
                        if(argument.getType() != null && argument.getType().length() >0){
                            Method[] methods = interfaceClass.getMethods();
                            //遍历所有方法
                            if(methods != null && methods.length > 0){
                                for (int i = 0; i < methods.length; i++) {
                                    String methodName = methods[i].getName();
                                    //匹配方法名称,获取方法签名.
                                    if(methodName.equals(method.getName())){
                                        Class[] argtypes = methods[i].getParameterTypes();
                                        //一个方法中单个callback
                                        if (argument.getIndex() != -1 ){
                                            if (argtypes[argument.getIndex()].getName().equals(argument.getType())){
                                                appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                                            }else {
                                                throw new IllegalArgumentException("argument config error : the index attribute and type attirbute not match :index :"+argument.getIndex() + ", type:" + argument.getType());
                                            }
                                        } else {
                                            //一个方法中多个callback
                                            for (int j = 0 ;j argclazz = argtypes[j];
                                                if (argclazz.getName().equals(argument.getType())){
                                                    appendParameters(map, argument, method.getName() + "." + j);
                                                    if (argument.getIndex() != -1 && argument.getIndex() != j){
                                                        throw new IllegalArgumentException("argument config error : the index attribute and type attirbute not match :index :"+argument.getIndex() + ", type:" + argument.getType());
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }else if(argument.getIndex() != -1){
                            appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                        }else {
                            throw new IllegalArgumentException("argument config must set index or type attribute.eg:  or ");
                        }

                    }
                }
            } // end of methods for
        }

        if (generic) {
            map.put("generic", String.valueOf(true));
            map.put("methods", Constants.ANY_VALUE);
        } else {
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put("revision", revision);
            }

            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if(methods.length == 0) {
                logger.warn("NO method found in service interface " + interfaceClass.getName());
                map.put("methods", Constants.ANY_VALUE);
            }
            else {
                map.put("methods", StringUtils.join(new HashSet(Arrays.asList(methods)), ","));
            }
        }
        if (! ConfigUtils.isEmpty(token)) {
            if (ConfigUtils.isDefault(token)) {
                map.put("token", UUID.randomUUID().toString());
            } else {
                map.put("token", token);
            }
        }
        if ("injvm".equals(protocolConfig.getName())) {
            protocolConfig.setRegister(false);
            map.put("notify", "false");
        }

  第三部分:
  这段主要是拼接URL,Dubbo框架是以URL为总线的模式,即运行过程中所有的状态数据信息都可以通过URL来获取,比如当前系统采用什么序列化,采用什么通信,采用什么负载均衡等信息,都是通过URL的参数来呈现的,所以在框架运行过程中,运行到某个阶段需要相应的数据,都可以通过对应的Key从URL的参数列表中获取。
本例url: dubbo://xxx.xxx.xxx.xxx:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demotest-provider&dubbo=2.5.3&interface=com.alibaba.dubbo.demo.DemoService&methods=getPermissions&organization=dubbox&owner=programmer&pid=35270&side=provider×tamp=1539154732792

        // 导出服务
        String contextPath = protocolConfig.getContextpath();//获取协议的上下文路径
        if ((contextPath == null || contextPath.length() == 0) && provider != null) {
            contextPath = provider.getContextpath();
        }
       // 根据参数创建url对象
        URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
       // 如果url使用的协议存在扩展,调用对应的扩展来修改原url。目前扩展有override,absent
        if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .hasExtension(url.getProtocol())) {
            url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                    .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
        }

       //根据scope的配置决定是作本地暴露还是远程暴露,
       //做服务暴露从结果上看就是产生了一个特定服务的 Exporter 类,
       //并将其存储在对应的ServiceBean实例的 exporters属性中。
        String scope = url.getParameter(Constants.SCOPE_KEY);
        //配置为none不暴露
        if (! Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {

            //配置不是remote的情况下做本地暴露 (配置为remote,则表示只暴露远程服务)
            if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
            //如果配置不是local则暴露为远程服务.(配置为local,则表示只暴露远程服务)
            if (! Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope) ){
                if (logger.isInfoEnabled()) {
                    logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                }
                //只有远程暴露才需要用到注册中心url
                if (registryURLs != null && registryURLs.size() > 0
                        && url.getParameter("register", true)) {
                    for (URL registryURL : registryURLs) {
                        url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
                        //获取监控中心的URL
                        URL monitorUrl = loadMonitor(registryURL);
                        if (monitorUrl != null) {
                            url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
                        }
                        if (logger.isInfoEnabled()) {
                            logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                        }
                        Invoker invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));

                        Exporter exporter = protocol.export(invoker);
                        exporters.add(exporter);
                    }
                } else {
                    Invoker invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);

                    Exporter exporter = protocol.export(invoker);
                    exporters.add(exporter);
                }
            }
        }
        this.urls.add(url);

本地暴露
此处demo,为本地暴露,此时,调用exportLocal(url);生成exporter

 private void exportLocal(URL url) {
        if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
            URL local = URL.valueOf(url.toFullString())
                    .setProtocol(Constants.LOCAL_PROTOCOL)
                    .setHost(NetUtils.LOCALHOST)
                    .setPort(0);
            //这里的protocol是Protocol$Adpative的实例(spi机制)
            //proxyFactory是ProxyFactory$Adpative实例(spi机制)
            Exporter exporter = protocol.export(
                    proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
            exporters.add(exporter);
            logger.info("Export dubbo service " + interfaceClass.getName() +" to local registry");
        }
    }

上述exportLocal方法参数url,为:
dubbo://xxx.xxx.xxx.xxx:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demotest-provider&dubbo=2.5.3&interface=com.alibaba.dubbo.demo.DemoService&methods=getPermissions&organization=dubbox&owner=programmer&pid=78965&side=provider×tamp=1539163125437
上述方法中URL local 重新构造url,本地暴露的url是以injvm开头的,host修改为本地,端口更改为0,这与远程发布时不同的,url更换为一下内容:
injvm://127.0.0.1/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demotest-provider&dubbo=2.5.3&interface=com.alibaba.dubbo.demo.DemoService&methods=getPermissions&organization=dubbox&owner=programmer&pid=78965&side=provider×tamp=1539163125437
这里的proxyFactory.getInvoker使用的是JavassistProxyFactory.getInvoker方法,
对服务接口进行透明代理,生成服务的客户端和服务端,使服务的远程调用就像在本地调用一样,默认使用JavassistProxyFactory,返回一个InvokerInvoker则是个可执行核心实体,Invokerinvoke方法通过反射执行service方法。

public class JavassistProxyFactory extends AbstractProxyFactory {

    @SuppressWarnings("unchecked")
    public  T getProxy(Invoker invoker, Class[] interfaces) {
        return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
    }
    //proxy 是服务实现类,type是服务接口
    public  Invoker getInvoker(T proxy, Class type, URL url) {
        ////利用Wrapper类通过服务接口生成对应的代理类。
        //根据传入的 proxy对象的类信息创建对它的包装对象Wrapper, Wrapper类不能正确处理带$的类名
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
        return new AbstractProxyInvoker(proxy, type, url) {
           //实现抽象类AbstractProxyInvoker抽象方法doInvoke,
          //并调用(proxy, type, url)构造函数实例化匿名类
            @Override
            protected Object doInvoke(T proxy, String methodName, 
                                      Class[] parameterTypes, 
                                      Object[] arguments) throws Throwable {

                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }

}

最后返回invoker后,使用protocol.export进行发布:
Protocol的实现是RegistryProtocol类,实现了export方法

类及方法的概念作用:
1、proxyFactory:就是为了获取一个接口的代理类,例如获取一个远程接口的代理。
  它有2个方法,代表2个作用
    a、getInvoker:针对server端,将服务对象,例如DemoServiceImpl包装成一个Wrapper对象。
    b、getProxy:针对client端,创建接口的代理对象,例如DemoService的接口。
2、makeWrapper:它类似spring的BeanWrapper,它就是包装了一个接口或一个类,可以通过Wrapper对实例对象进行赋值取值以及指定方法的调用。
3、Invoker:它是一个可执行的对象,能够根据方法名称、参数得到相应的执行结果。它里面有一个很重要的方法Result invoke(Invocation invocation)Invocation是包含了需要执行的方法和参数的重要信息,目前它只有2个实现类RpcInvocation、 MockInvocation
    它有3种类型的Invoker
      1、本地执行类的Invoker
      2、远程通信类的Invoker
      3、多个远程通信执行类的Invoker聚合成集群版的Invoker
4、Protocol:
    1)export暴露远程服务(用于服务端),就是将proxyFactory.getInvoker创建的代理类invoker对象,通过协议暴露给外部。
    2)refer:引用远程服务(用于客户端)
5、Exporter:维护invoker的生命周期
6、exchanger:信息交换层,封装请求响应模式同步转异步
7、transporter:网络传输层,用来抽象Netty(dubbo默认)或者Mina的统一接口

暴露本地服务与暴露远程服务的区别

a、暴露本地服务:指暴露在同一个JVM里面,不用通过zk来进行远程通信,例如在同一个服务里面(tomcat),自己调用自己的接口,就无需进行网络IP连接通信。
b、暴露远程服务:指暴露给远程客户端的IP和端口号,通过网络来实现通信。

远程暴露

doExportUrlsFor1Protocol方法中if (! Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope) )条件,如果配置不是local则暴露为远程服务.(配置为local,则表示只暴露远程服务)
public static final String SCOPE_LOCAL = "local";

未完待续

欢迎关注公众号


dubbo源码学习(1)--服务发布_第9张图片
image.png

你可能感兴趣的:(dubbo源码学习(1)--服务发布)