2.Dubbo源码阅读-配置篇

Dubbo的分支: 3.0
Dubbo的服务提供者会将RPC服务的调用说明,导出到配置中心。然后服务的消费者向配置中心订阅这些服务,也就是引用这些服务。

服务端-服务提供方-暴露/导出服务

  1. dubbo根据spring的扩展api,增加了dubbo命名空间,以及各种xml元素,用来配置服务。

    1. 关于dubbo和spring的集成,以及spring对自定义的命名空间解析,详见NamespaceHandler,NamespaceHandlerSupport的使用。
  2. spring boot启动,解析dubbo框架xml配置文件,装载实例,放入spring容器,到这相当于实例化bean完成。

  3. dubbo增加了应用上下文监听类DubboBootstrapApplicationListener,类内覆盖onApplicationContextEvent(ApplicationContextEvent event)方法,启动dubbo。

    @Override
    public void onApplicationContextEvent(ApplicationContextEvent event) {
        //使用事件监听器的方式与spring集成
        if (event instanceof ContextRefreshedEvent) {
            onContextRefreshedEvent((ContextRefreshedEvent) event);
        } else if (event instanceof ContextClosedEvent) {
            onContextClosedEvent((ContextClosedEvent) event);
        }
    }
    
    private void onContextRefreshedEvent(ContextRefreshedEvent event) {
        //启动dubbo
        dubboBootstrap.start();
    }
    
    
  4. org.apache.dubbo.config.bootstrap.DubboBootstrap#start

    1. 如果是第一次启动的话,调用initialize()完成spi加载、配置中心初始化、事件监听等初始化。

      public DubboBootstrap start() {
          if (started.compareAndSet(false, true)) {
              startup.set(false);
              initialize();
              //start方法内有两行关键的代码,一个是服务提供方开始暴露/导出所有服务,一个是消费方开始发现/引用服务
           
              // 1. 导出dubbo所有服务
              exportServices();
      
              // Not only provider register
              if (!isOnlyRegisterProvider() || hasExportedServices()) {
                  // 2. export MetadataService
                  exportMetadataService();
                  //3. Register the local ServiceInstance if required
                  registerServiceInstance();
              }
      
              //消费者引用服务
              referServices();
              
              //省略...
          }
          return this;
      }
      
    2. 调用exportServices暴露/导出服务。

      private void exportServices() {
          //从配置中心拿到配置的所有service bean,依次导出。
          configManager.getServices().forEach(sc -> {
              ServiceConfig serviceConfig = (ServiceConfig) sc;
              serviceConfig.setBootstrap(this);
      
              if (exportAsync) {
                  //异步导出
                  ExecutorService executor = executorRepository.getServiceExporterExecutor();
                  Future<?> future = executor.submit(() -> {
                      sc.export();
                      exportedServices.add(sc);
                  });
                  asyncExportingFutures.add(future);
              } else {
                  //同步导出
                  sc.export();
                  exportedServices.add(sc);
              }
          });
      }
      
    3. 遍历所有服务,依次调用org.apache.dubbo.config.ServiceConfig#export,导出服务。最终调用org.apache.dubbo.config.ServiceConfig#doExportUrls,对该服务配置的暴露协议,每个协议暴露一份服务。

      private void doExportUrls() {
          //repository.services存有所有的服务描述对象,包括默认加载的隐含服务,
          //如EchoService,GenericService,MonitorService,MetricsService
          //repository.providers存有所有的服务提供者对象
          //repository.consumers存有所有的服务消费者对象
          ServiceRepository repository = ApplicationModel.getServiceRepository();
          
          //此处省略部分代码......
          
          //获取向配置中心注册的服务url格式,即registry://xxxx
          List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
      
          for (ProtocolConfig protocolConfig : protocols) {
              //此处省略部分代码......
              //因为一个服务可以使用多种通讯协议,所以此处是每个协议导出一份服务配置,如dubbo/http/injvm协议各一份
              doExportUrlsFor1Protocol(protocolConfig, registryURLs);
          }
      }
      
    4. 导出的核心方法:org.apache.dubbo.config.ServiceConfig#doExportUrlsFor1Protocol

      1. 因为dubbo设计的是使用url作为配置总线,代表最终的导出格式。这个方法会先补充url信息,如失败重试retry、token、Generic服务等数据。
      2. 判断一个服务只要不是配置了只能暴露给远程的话,就会导出一份本地injvm服务,即调用exportLocal(url)。
      3. 暴露给远程。

单独说下导出一个服务的主要逻辑

  1. 这里需要先解释下dubbo设计的几个类:Protocol/Exporter/Invoker,说下这三个类的关系。

    1. Protocol是协议的意思,比如Dubbo、Injvm协议。一个服务要以某个协议的方式暴露出来,即可以用这个协议来访问这个服务,如HelloService以http协议的方式暴露出来,也就是以后可以用http的方式访问这个服务。
    2. Dubbo抽象出来一个Exporter的概念,负责如何暴露服务,如何取消服务暴露。 一个服务被暴露出来,需要知道它该怎么调用,就有了Invoker。Invoker负责实际的调用服务返回响应。
    3. 综上,1个Protocol包含多个服务的Exporter,1个Exporter在大部分情况下只包含1个Invoker。
    4. 从代码来看三者的关系:
    public abstract class AbstractProtocol implements Protocol {
    
        //该协议的所有导出者,即暴露的所有服务
        //"greeting/org.apache.dubbo.demo.GreetingService:1.0.0:20880" -> DubboExporter对象
        //"greeting/org.apache.dubbo.demo.GreetingService:1.0.0" -> InjvmExporter对象
        protected final Map<String, Exporter<?>> exporterMap = new ConcurrentHashMap<String, Exporter<?>>();
    
        //省略......
    }
    
    public abstract class AbstractExporter<T> implements Exporter<T> {
    
        //每个AbstractExporter子类都会至少包含一个Invoker
        private final Invoker<T> invoker;
    
        //省略......
    }
    
  2. 再说下这三者的组装过程:

    public class ServiceConfig<T> extends ServiceConfigBase<T> {
        //自适应Protocol,默认以DubboProtocol为基础,按优先级在外层包装各种包装类,如:ProtocolFilterWrapper,ProtocolListenerWrapper,QosProtocolWrapper
        private static final Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
    
        //Invoker动态代理工厂,默认为JavassistProxyFactory
        private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
    
        //所有导出的服务
        private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>();
    
        private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
            //省略...
            if (CollectionUtils.isNotEmpty(registryURLs)) {
                for (URL registryURL : registryURLs) {
                    //省略...
                    //使用JavassistProxyFactory动态生成、加载invoker字节码。以GreetingService举例
                    //ref=greetingServiceImpl,interfaceClass=GreetingService.class,第三个参数是url=injvm://127.0.0.1/org.apache.dubbo.demo.GreetingService ?anyhost=true&application=demo-provider&bind.ip=192.168.2.3&bind.port=20880 &deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting &interface=org.apache.dubbo.demo.GreetingService&mapping-type=metadata &mapping.type=metadata&metadata-type=remote&methods=hello&pid=25062&qos.port=22222 &release=&revision=1.0.0&side=provider&timeout=5000&timestamp=1615949370199&version=1.0.0
                    //JavassistProxyFactory.getInvoker方法内部会先生成invoker wrapper包装类,目的是统一bean的方法调用。生成的wrapper类详见下面Wrapper1。
                    Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
    
                    //使用JavassistProxyFactory动态生成、加载invoker字节码。以GreetingService举例
                    //ref=greetingServiceImpl,interfaceClass=GreetingService.class,
                    //第三个参数是url=injvm://127.0.0.1/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider
                    // &bind.ip=192.168.2.3&bind.port=20880 &deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting
                    // &interface=org.apache.dubbo.demo.GreetingService&mapping-type=metadata &mapping.type=metadata
                    // &metadata-type=remote&methods=hello&pid=25062&qos.port=22222 &release=&revision=1.0.0&side=provider
                    // &timeout=5000&timestamp=1615949370199&version=1.0.0
                    //JavassistProxyFactory.getInvoker方法内部会先生成invoker wrapper包装类。生成的wrapper类详见下面Wrapper1。
                    Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
    
                    //将invoker以某个协议暴露出去,返回exporter
                    //在protocol的多层包装对象中,有一个ProtocolFilterWrapper,调用export方法时,它会先判断是否为registry协议,如果不是,
                    //需要先构造invoker的过滤器链,增强invoker功能,如MonitorFilter,TimeoutFilter,TraceFilter,ExceptionFilter等,再继续导出。
                    Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
                    exporters.add(exporter);
                }
            } else {
                //省略,基本同上
                Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
                DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
    
                Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
                exporters.add(exporter);
            }
        }
    }
    
  3. 服务如何暴露给远程,即向公共配置中心注册服务

    1. 上面说过我们在xml中配置一个服务,可以指定它以哪些协议的方式暴露出来,比如dubbo,injvm,http,grpc,hessian等

    2. dubbo会遍历所有协议,每一个协议都会调用doExportUrlsFor1Protocol()来导出对应格式的服务。

    3. dubbo会判断如果这个协议配置了暴露给远程,那么会根据当前协议的url,创建一个用于注册registry的url。如,当前是http协议,框架判断需要暴露给远程,即需要注册到公共配置中心,那么会根据http://xxx,生成一个registry://xxx的url,用于注册服务。

    4. 服务导出是由协议对象调用export()触发,Exporter<?> exporter = PROTOCOL.export(wrapperInvoker)。其中registry://协议是由RegistryProtocol.export负责导出。

      @Override
      public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
          //orginInvoker的url=registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?xxx
          //得到registryUrl=zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?xxx
          URL registryUrl = getRegistryUrl(originInvoker);
      
          //得到providerUrl=dubbo://192.168.2.3:20880/org.apache.dubbo.demo.GreetingService?xxx
          URL providerUrl = getProviderUrl(originInvoker);
      
          //省略...
      
          // decide if we need to delay publish
          boolean register = providerUrl.getParameter(REGISTER_KEY, true);
          if (register) {
              //立即向配置中心如zk,注册服务
              register(registryUrl, registeredProviderUrl);
          }
      
          //省略...
      
          //Ensure that a new exporter instance is returned every time export
          return new DestroyableExporter<>(exporter);
      }
      
      private void register(URL registryUrl, URL registeredProviderUrl) {
          //根据url协议头,获取配置中心注册器对象,得到ZookeeperRegistry
          Registry registry = registryFactory.getRegistry(registryUrl);
          //调用ZookeeperRegistry的注册方法,内部为使用CuratorZookeeperClient向配置中心写服务数据
          //到这里也就完成了将服务注册到公共配置中心,暴露给远程了
          registry.register(registeredProviderUrl);
      }
      
    5. 到此服务暴露完毕。

客户端-服务消费方-发现/引用服务

先需要理解消费方的几个重要概念

  1. 先举个例子,借着例子来理解。假设在北京和上海分别有一个注册中心zk-beijing,zk-shanghai。在北京有三台机器,ip分别为1.1.1.1/2.2.2.2/3.3.3.3。其中1.1.1.1为消费者,2.2.2.2和3.3.3.3为服务提供者。2.2.2.2提供ServiceA,ServiceB两个服务,3.3.3.3提供ServiceA,ServiceC两个服务。
    1. 我们先简化环境,看下只有一个注册中心的情况,即只向zk-beijing注册。在2.2.2.2/3.3.3.3启动初始化时,会向注册中心注册暴露服务,路径为:

      //zookeeper路径结构
      /dubbo
          /ServiceA
              /providers
                  /URL.encode(dubbo://2.2.2.2:20880/ServiceA?anyhost=true&application=demo-provider&delay=5000&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=ServiceA&mapping-type=metadata&mapping.type=metadata&metadata-type=remote&methods=sayHello,sayHelloAsync&pid=37004&release=&side=provider&timeout=3000&timestamp=1617773391401)
                  /URL.encode(dubbo://3.3.3.3:20880/ServiceA?anyhost=true&application=demo-provider&delay=5000&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=ServiceA&mapping-type=metadata&mapping.type=metadata&metadata-type=remote&methods=sayHello,sayHelloAsync&pid=35111&release=&side=provider&timeout=3000&timestamp=1617773391507)
          /ServiceB
              /providers
                  /URL.encode(rest://2.2.2.2:80/ServiceB?anyhost=true&application=demo-provider&delay=5000&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=ServiceB&mapping-type=metadata&mapping.type=metadata&metadata-type=remote&methods=buy,sell&pid=37004&release=&revision=1.0.0&side=provider&timeout=5000&timestamp=1617773396526&version=1.0.0)
          /ServiceC
              /providers
                  /URL.encode(dubbo://3.3.3.3:20880/ServiceC?anyhost=true&application=demo-provider&delay=5000&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=ServiceC&mapping-type=metadata&mapping.type=metadata&metadata-type=remote&methods=go,back&pid=351111&release=&side=provider&timeout=3000&timestamp=1617773391507)
      
    2. 从路径可以看出,dubbo不是以一个机器为基本单位,暴露该机器下有哪些服务的,如:/dubbo/2.2.2.2/我的服务xxx。dubbo是以服务为单位来暴露。因为我们引用都是引用哪个服务,暴露也应该是暴露哪个服务,服务下标明哪些机器上有该服务。你需要引用哪些服务就可以随意的引用哪些,不需要引用整个机器暴露出来的所有服务。假如一个服务暴露在多台机器上,就更是个问题了。

    3. 再看多注册中心,假如有另外几台机器,向shanghai注册,情况和beijing差不多。这种多注册中心存在时,可以看做有两个异地服务集群,比如HelloService有北京和上海两个服务集群。

概念一:Invoker

  1. Invoker是什么:从zk树上看,/providers下的每一个服务url在消费方都会对应一个Invoker。如果1.1.1.1引用了这三个服务,那就会创建4个对应的Invoker。一个Invoker表示一个消费方持有的,对一个提供方某一个服务的引用。Invoker内包含连接池属性字段ExchangeClient[] clients。因为ClusterInvoker的存在,为了区分,Invoker也叫做普通Invoker。

概念二:Directory

  1. Directory就是多个Invoker的集合,实现类一般会有一个List<Invoker<T>>属性字段,存储Invoker。为了方便表示以及操作多Invoker,抽象出来的概念。

概念三:Router

  1. Router路由。在实际使用中,可能由于我们部署的物理机性能有差别,有几台机器性能特别好,或者说为了测试某几台机器上的服务,要求请求更多的或者全部路由到某几台机器,Router负责的就是这个路由逻辑。Router会从Directory中遍历选出与路由规则匹配的invoker子集,然后交给负载均衡器。

概念四:LoadBalance

  1. LoadBalance(负载均衡器)接收Router筛选出来的可以使用的invoker集合,从中选出一个普通invoker,用于发起实际调用。

概念五:ClusterInvoker

  1. ClusterInvoker就是实际的某一个物理集群了,如HelloService服务的北京集群。

概念六:Cluster

  1. Cluster是对实际物理集群抽象出的最上层概念,即某一个服务的所有物理集群的集合叫做Cluster。如HelloService的北京和上海两个异地物理集群合起来叫做Cluster。

总结

  1. HelloService服务部署在两个异地集群,一个北京,一个上海。两地各自都有一个注册中心zk。
  2. 自下而上的组装过程:
    1. dubbo会从北京的注册中心zk读取HelloService的所有提供者,一个提供者封装成一个普通Invoker。
    2. 将多个普通Invoker存储到一个Directory中。
    3. 根据业务特性,创建一个对应的集群执行器对象ClusterInvoker。选择适合的Router和LoadBalance,将Directory传入。到此北京集群的invoker封装好了。
    4. 重复上述步骤,将上海集群也封装成一个ClusterInvoker。
    5. 将北京、上海ClusterInvoker封装到多注册中心Cluster对象中。
    6. 服务以Cluster为入口,对外提供服务。
  3. 自上而下的调用过程:
    1. Cluster会根据订阅的各个注册中心的配置、与服务消费方是否处于同一个物理集群、权重等先选出来一个调用的ClusterInvoker,即先选出来一个物理集群。假如选出来的是北京集群。

    2. 然后在北京集群ClusterInvoker执行Router路由,选出匹配的普通Invoker集合。

    3. 在路由后的Invoker集合上,执行负载均衡,由LoadBalance选出一个最终用来执行RPC的普通Invoker。

    4. 执行Invoker.invoke(Invocation invocation),发起调用。

      //从代码看下调用过程
      public abstract class AbstractClusterInvoker<T> implements ClusterInvoker<T> {
          public Result invoke(final Invocation invocation) throws RpcException {
              //省略
              //使用路由器Router从ClusterInvoker.directory存储的所有的invoker中,路由出符合路由规则的一批invoker。
              List<Invoker<T>> invokers = list(invocation);
              //上面选出了方向正确的一批invoker,现在需要负载均衡选一个执行了。此处初始化负载均衡器LoadBalance。
              LoadBalance loadbalance = initLoadBalance(invokers, invocation);
              //为异步执行附着调用id
              RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
              //使用负载均衡器选一个invoker,发起调用,返回响应。
              return doInvoke(invocation, invokers, loadbalance);
          }
      }
      

消费方发现/引用服务的过程

  1. Dubbo是严重依赖Spring框架的,我们在xml配置了java interface > HelloService的Bean声明和引用关系后,如何在服务消费方生成、加载该接口的实现类,创建实例对象,纳入到Spring容器管理,在需要的地方注入。
  2. Dubbo是由FactoryBean入手的,过程与MyBatis的Mapper类生成过程类似。(注:3.0分支中,只有延迟加载的代理对象是使用FactoryBean功能生成的,普通代理对象是使用ReferenceConfig.get()生成的。而在master分支中,所有代理对象都是使用FactoryBean生成,底层实现也是调用的ReferenceConfig.get())
  3. 下面说的是3.0分支普通代理对象的生成过程,没有使用FactoryBean。
  1. 与服务暴露一样,服务引用也是使用事件监听的方式与spring集成,都会调用的org.apache.dubbo.config.bootstrap.DubboBootstrap#start方法,该方法上面说过,有两个关键的点,一个是exportServices():提供方开始暴露/导出所有服务,另一个是referServices():消费方开始发现/引用服务。

    public DubboBootstrap start() {
        if (started.compareAndSet(false, true)) {
            startup.set(false);
            initialize();
            //start方法内有两行关键的代码,一个是服务提供方开始暴露/导出所有服务,一个是消费方开始发现/引用服务
         
            // 1. 导出dubbo所有服务
            exportServices();
    
            // Not only provider register
            if (!isOnlyRegisterProvider() || hasExportedServices()) {
                // 2. export MetadataService
                exportMetadataService();
                //3. Register the local ServiceInstance if required
                registerServiceInstance();
            }
    
            //消费者引用服务
            referServices();
            
            //省略...
        }
        return this;
    }
    
  2. referServices()中会触发缓存数据的初始化、加载。

    private void referServices() {
        //configManager.getReferences()拿到的是对xml中配置的所有<dubbo:reference xxx/>
        //解析后封装的ReferenceConfig对象
        configManager.getReferences().forEach(rc -> {
            //省略...
            //cache为ReferenceConfigCache类型,底层是一个ConcurrentHashMap,
            //调用get会初始化对应的ReferenceConfig对象,放入缓存中
            cache.get(rc);
        });
    }
    
  3. ReferenceConfigCache.get(rc),从缓存拿消费方引用的服务代理对象,如果没有触发实现类的生成、加载,然后创建代理类放入cache。

    public <T> T get(ReferenceConfigBase<T> referenceConfig) {
        //key=org.apache.dubbo.demo.DemoService
        String key = generator.generateKey(referenceConfig);
        //type=DemoService.class
        Class<?> type = referenceConfig.getInterfaceClass();
        //proxies结构=< DemoService.class, <"org.apache.dubbo.demo.DemoService", DemoService实现类代理对象> >
        proxies.computeIfAbsent(type, _t -> new ConcurrentHashMap<>());
        //proxiesOfType结构=<"org.apache.dubbo.demo.DemoService", DemoService实现类代理对象>
        ConcurrentMap<String, Object> proxiesOfType = proxies.get(type);
        proxiesOfType.computeIfAbsent(key, _k -> {
            //由ReferenceConfig配置对象触发对应服务接口实现类的创建、加载,以及代理对象的创建。
            //proxy就是在消费方引用的服务代理对象
            Object proxy = referenceConfig.get();
            referredReferences.put(key, referenceConfig);
            return proxy;
        });
    
        return (T) proxiesOfType.get(key);
    }
    
  4. 关键的来了,ReferenceConfig类包含了生成、加载实现类,创建代理对象的主要逻辑。

    public class ReferenceConfig<T> extends ReferenceConfigBase<T> {
        //缺省FailoverCluster
        private static final Cluster CLUSTER = ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension();
        //缺省JavassistProxyFactory
        private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
        //根据url协议类型决定,url=registry://xxx,对应RegistryProtocol。url=dubbo://xxx,对应DubboProtocol。url=injvm://xxx,对应InjvmProtocol。
        private static final Protocol REF_PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
        //ClusterInvoker类型
        private transient volatile Invoker<?> invoker;
        //直连提供者地址或者注册中心地址
        protected final List<URL> urls = new ArrayList<URL>();
        //引用的代理对象
        private transient volatile T ref;
        //1.生成的代理对象会set到属性字段中,为null的话,开始引用初始化。
        public synchronized T get() {
            if (ref == null) {
                //触发引用初始化
                init();
            }
            return ref;
        }
        //2. 初始化
        public synchronized void init() {
            if (initialized) return;
    
            //解析各种消费方配置,放入map
            //省略...
    
            //map={"mapping-type":"metadata","init":"false","side":"consumer","register.ip":"192.168.2.3",
            // "release":"","methods":"sayHello,sayHelloAsync","qos.port":"33333","provided-by":"demo-provider",
            // "dubbo":"2.0.2","pid":"35116","check":"true","interface":"org.apache.dubbo.demo.DemoService",
            // "enable.auto.migration":"true","mapping.type":"metadata","metadata-type":"remote",
            // "application":"demo-consumer","sticky":"false","timestamp":"1617676272015","enable-auto-migration":"true"}
            //为org.apache.dubbo.demo.DemoService接口创建消费方代理对象
            ref = createProxy(map);
    
            //分发ReferenceConfigInitializedEvent事件
            dispatch(new ReferenceConfigInitializedEvent(this, invoker));
        }
    
        //3.创建动态代理对象
        private T createProxy(Map<String, String> map) {
            //从配置中找出是否应该引用同一JVM中的服务。默认行为为true
            if (shouldJvmRefer(map)) {
                //injvm本地引用
                URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
                //InjvmProtocol.refer返回InjvmInvoker类型
                invoker = REF_PROTOCOL.refer(interfaceClass, url);
            } else {
                //远程引用
                //url为配置的peer-to-peer调用地址,或者注册中心地址
                //假如服务集群有5台机器,你只想测试调用其中的A,B两台机器,配置的就是直连提供者AB的地址
                //<dubbo:reference id="as" interface="AService" url="dubbo://1.1.1.1:20890;第二提供者地址"/>
    
                if (urls.size() == 1) {
                    //一个服务只有一个url,有两种情况:1.只有一个注册中心;2.直连一个提供者。
                    //这里返回的invoker一定是ClusterInvoker子类型,默认为FailoverClusterInvoker,
                    //注册中心的提供者或者直连url都会转为普通invoker->directory,再set到ClusterInvoker中返回
                    //如果url.protocol=service-discovery-registry,对应的protocol为:RegistryProtocol
                    invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
                } else {
                    //一个服务多个url,可能有三种情况:1.多个注册中心;2.直连多个提供者。3.两者共存,即url配了注册中心地址和直连提供者地址,这种情况不确定可不可以,没试出来。
                    //如果url中有注册协议,即从注册中心订阅服务,返回的invoker为ZoneAwareClusterInvoker类型
                    //无注册协议时,返回FailoverClusterInvoker
                    if (registryURL != null) {
                        String cluster = registryURL.getParameter(CLUSTER_KEY, ZoneAwareCluster.NAME);
                        invoker = Cluster.getCluster(cluster, false).join(new StaticDirectory(registryURL, invokers));
                    } else {
                        String cluster = "省略...";
                        invoker = Cluster.getCluster(cluster).join(new StaticDirectory(invokers));
                    }
                }
            }
    
            //JavassistProxyFactory.getProxy
            return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));
        }   
    }
    
  5. 到这就完成了消费者引用服务代理对象,其中Cluster和Invoker的组装过程,就是上面提到的。

  6. 补充下master分支中,改版的使用FactoryBean生成代理对象逻辑:

    public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean,
            ApplicationContextAware, InitializingBean, DisposableBean {
    
        @Override
        public Object getObject() {
            //调用ReferenceConfig.get()生成代理对象
            return get();
        }
    
        @Override
        public Class<?> getObjectType() {
            //返回服务接口class
            return getInterfaceClass();
        }
    
    }
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,056评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,842评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,938评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,296评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,292评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,413评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,824评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,493评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,686评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,502评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,553评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,281评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,820评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,873评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,109评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,699评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,257评论 2 341

推荐阅读更多精彩内容