深入理解Dubbo-6.服务消费源码分析

  • 👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家
  • 📕系列专栏:Spring源码、JUC源码、Kafka原理、分布式技术原理
  • 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
  • 🍂博主正在努力完成2023计划中:源码溯源,一探究竟
  • 📝联系方式:nhs19990716,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬👀

客户端注册

实现猜想

  1. 生成远程服务的代理
  2. 获得目标服务的url地址
  3. 还需要建立和注册中心的动态感知
  4. 网络连接的建立
  5. 服务通信的过程中
  • filter过滤
  • 实现负载均衡
  • 实现集群容错

注入方式

Dubbo的服务消费者注入也有两种方式:

  • 通过xml形式
  • 基于注解的方式
java 复制代码
@RestController
public class SayController {

    @DubboReference(registry = {"shanghai","hunan"},
            protocol = "dubbo",
            loadbalance = "consistenthash",
            mock = "com.gupaoedu.springboot.dubbo.springbootdubbosampleconsumer.MockSayHelloService",
            timeout = 500,
            cluster = "failfast",check = false,methods = {
            @Method(loadbalance = "",name ="" )
    },retries = 5)
    ISayHelloService sayHelloService;

    @GetMapping("/say")
    public String say(){
        return sayHelloService.sayHello("Mic");
    }

}

当前Bean被加载的时候,去识别这个Bean里面的成员变量的时候,需要去扫描这个注解@DubboReference。

所以首先会在DubboAutoConfiguration中配置一个自动装配机制

DubboAutoConfiguration

java 复制代码
@ConditionalOnMissingBean
@Bean(name = ReferenceAnnotationBeanPostProcessor.BEAN_NAME)
public ReferenceAnnotationBeanPostProcessor
	referenceAnnotationBeanPostProcessor() {
		return new ReferenceAnnotationBeanPostProcessor();
	}

// 会将这三种不同的注解都传过去,要识别的注解类型是哪些?
public ReferenceAnnotationBeanPostProcessor() {
        super(new Class[]{DubboReference.class, Reference.class, com.alibaba.dubbo.config.annotation.Reference.class});
    }

最终会执行 ReferenceAnnotationBeanPostProcessor 中的重写方法 doGetInjectedBean ,也就是实现bean的依赖注入的方法。

(前置介绍:

ReferenceAnnotationBeanPostProcessor 是一个 Spring Bean 后置处理器,它实现了 BeanPostProcessor 接口。当 Spring 容器创建完一个 Bean 后,会自动调用所有注册的后置处理器的 postProcessBeforeInitializationpostProcessAfterInitialization 方法,来对 Bean 进行前置/后置处理。

在这里,ReferenceAnnotationBeanPostProcessor 的主要作用是对使用 DubboReferenceReference 或者 com.alibaba.dubbo.config.annotation.Reference 标注的属性进行依赖注入,这些属性都表示 Dubbo RPC 服务的引用。为了实现依赖注入,ReferenceAnnotationBeanPostProcessor 需要在 Bean 创建完成后,扫描 Bean 中的属性,检查是否有需要注入的 Dubbo 引用,并通过 Dubbo 的引用获取相应的实例,将其注入到 Bean 的属性中。

具体来说,当 Spring 容器创建完一个 Bean 后,ReferenceAnnotationBeanPostProcessorpostProcessAfterInitialization 方法会被调用,在该方法中,会遍历 Bean 中的所有属性,检查是否使用了 Dubbo 引用注解(如 DubboReference),如果发现有,就会调用 doGetInjectedBean 方法,来完成对该属性值的注入。这个过程需要通过 Dubbo 的引用获取相应的实例,以完成依赖注入。)

doGetInjectedBean

在这个方法中,主要做两个事情

  • 注册一个ReferenceBean到Spring IOC容器中
  • 调用 getOrCreateProxy 返回一个动态代理对象
java 复制代码
protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType, InjectedElement injectedElement) throws Exception {
    	// 根据注解的属性和注入类型,通过 buildReferencedBeanName 方法构建引用 Bean 的名称
        String referencedBeanName = this.buildReferencedBeanName(attributes, injectedType);
    	// 获取标记有 @Reference 注解的属性上指定的 Bean 名称
        String referenceBeanName = this.getReferenceBeanName(attributes, injectedType);
    	// 根据指定的 referenceBeanName、注解属性和注入类型构建 ReferenceBean 实例 (referenceBean),如果该实例不存在的话。接下来,通过调用 isLocalServiceBean 方法,判断 referencedBeanName 是否是本地服务 Bean。
        ReferenceBean referenceBean = this.buildReferenceBeanIfAbsent(referenceBeanName, attributes, injectedType);
        boolean localServiceBean = this.isLocalServiceBean(referencedBeanName, referenceBean, attributes);
    
    // 将 referenceBean 注册到 Spring 容器中,并设置相关属性,如是否是本地服务 Bean、注入类型等。
        this.registerReferenceBean(referencedBeanName, referenceBean, attributes, localServiceBean, injectedType);
        this.cacheInjectedReferenceBean(referenceBean, injectedElement);
    	// 根据引用的 Bean 名称 (referencedBeanName)、referenceBean、是否是本地服务 Bean (localServiceBean) 和注入类型 (injectedType),获取或创建动态代理。这个动态代理可以用于在执行方法调用时进行远程调用。
        return this.getOrCreateProxy(referencedBeanName, referenceBean, localServiceBean, injectedType);
    }

getOrCreateProxy

获取或者创建一个动态代理对象。

java 复制代码
private Object getOrCreateProxy(String referencedBeanName, ReferenceBean referenceBean, boolean localServiceBean, Class<?> serviceInterfaceType) {
        if (localServiceBean) {
            // 如果是本地服务bean,则new一个动态代理
            return Proxy.newProxyInstance(this.getClassLoader(), new Class[]{serviceInterfaceType}, this.newReferencedBeanInvocationHandler(referencedBeanName));
        } else {
            this.exportServiceBeanIfNecessary(referencedBeanName);
            return referenceBean.get();
        }
    }

可以看到,最终返回的动态代理对象,是通过referenceBean.get();来获得的。

ReferenceConfig.get

java 复制代码
public synchronized T get() {
        if (this.destroyed) {
            throw new IllegalStateException("The invoker of ReferenceConfig(" + this.url + ") has already destroyed!");
        } else {
            if (this.ref == null) {
                this.init();
            }

            return this.ref;
        }
    }

init方法

开始调用init方法进行ref也就是代理对象的初始化动作.

  • 检查配置信息
  • 根据dubbo配置,构建map集合。
  • 调用 createProxy 创建动态代理对象
java 复制代码
public synchronized void init() {
    //如果已经初始化,则直接返回
        if (!this.initialized) {
            if (this.bootstrap == null) {
                this.bootstrap = DubboBootstrap.getInstance();
                this.bootstrap.init();
            }

            //检查配置
            this.checkAndUpdateSubConfigs();
            //检查本地存根 local与stub
            this.checkStubAndLocal(this.interfaceClass);
            ConfigValidationUtils.checkMock(this.interfaceClass, this);
            Map<String, String> map = new HashMap();
            map.put("side", "consumer");
            //添加运行时参数
            ReferenceConfigBase.appendRuntimeParameters(map);
            if (!ProtocolUtils.isGeneric(this.generic)) {
                //获取版本信息
                String revision = Version.getVersion(this.interfaceClass, this.version);
                if (revision != null && revision.length() > 0) {
                    map.put("revision", revision);
                }
				//获取接口方法列表,添加到map中
                String[] methods = Wrapper.getWrapper(this.interfaceClass).getMethodNames();
                if (methods.length == 0) {
                    logger.warn("No method found in service interface " + this.interfaceClass.getName());
                    map.put("methods", "*");
                } else {
                    map.put("methods", StringUtils.join(new HashSet(Arrays.asList(methods)), ","));
                }
            }
            // 其实这里和服务端一样,也需要解析配置信息拼接成url,可以通过注册中心拿到配置信息,也可以使用自己配置的信息
			//通过class加载配置信息
            map.put("interface", this.interfaceName);
            AbstractConfig.appendParameters(map, this.getMetrics());
            AbstractConfig.appendParameters(map, this.getApplication());
            AbstractConfig.appendParameters(map, this.getModule());
            AbstractConfig.appendParameters(map, this.consumer);
            AbstractConfig.appendParameters(map, this);
            //将元数据配置信息放入到map中
            MetadataReportConfig metadataReportConfig = this.getMetadataReportConfig();
            if (metadataReportConfig != null && metadataReportConfig.isValid()) {
                map.putIfAbsent("metadata-type", "remote");
            }
			//遍历methodConfig,组装method参数信息
            Map<String, AsyncMethodInfo> attributes = null;
            if (CollectionUtils.isNotEmpty(this.getMethods())) {
                attributes = new HashMap();
                Iterator var4 = this.getMethods().iterator();

                while(var4.hasNext()) {
                    MethodConfig methodConfig = (MethodConfig)var4.next();
                    AbstractConfig.appendParameters(map, methodConfig, methodConfig.getName());
                    String retryKey = methodConfig.getName() + ".retry";
                    if (map.containsKey(retryKey)) {
                        String retryValue = (String)map.remove(retryKey);
                        if ("false".equals(retryValue)) {
                            map.put(methodConfig.getName() + ".retries", "0");
                        }
                    }

                    AsyncMethodInfo asyncMethodInfo = AbstractConfig.convertMethodConfig2AsyncInfo(methodConfig);
                    if (asyncMethodInfo != null) {
                        attributes.put(methodConfig.getName(), asyncMethodInfo);
                    }
                }
            }
            
			//获取服务消费者ip地址
            String hostToRegistry = ConfigUtils.getSystemProperty("DUBBO_IP_TO_REGISTRY");
            if (StringUtils.isEmpty(hostToRegistry)) {
                hostToRegistry = NetUtils.getLocalHost();
            } else if (NetUtils.isInvalidLocalHost(hostToRegistry)) {
                throw new IllegalArgumentException("Specified invalid registry ip from property:DUBBO_IP_TO_REGISTRY, value:" + hostToRegistry);
            }

            map.put("register.ip", hostToRegistry);
            this.serviceMetadata.getAttachments().putAll(map);
            
            this.ref = this.createProxy(map);
            
            this.serviceMetadata.setTarget(this.ref);
            this.serviceMetadata.addAttribute("refClass", this.ref);
            ConsumerModel consumerModel = this.repository.lookupReferredService(this.serviceMetadata.getServiceKey());
            consumerModel.setProxyObject(this.ref);
            consumerModel.init(attributes);
            this.initialized = true;
            this.dispatch(new ReferenceConfigInitializedEvent(this, this.invoker));
        }
    }

createProxy

我们先来思考一下,创建动态代理对象这个过程中,它可能会有哪些操作步骤?这个方法要能猜出来,那必然需要对dubbo的使用比较熟悉。

首先我们需要注意一个点,这里是创建一个代理对象,而这个代理对象应该也和协议有关系,也就是不同的协议,使用的代理对象也应该不一样。

观察下面的代码,我们发现没有这么简单,正常创建动态代理,通过那两种方式即可去构建就行,但是这里会有很多前置的东西。

java 复制代码
private T createProxy(Map<String, String> map) {
        URL u;
    // 是不是同JVM调用
        if (this.shouldJvmRefer(map)) {
            URL url = (new URL("injvm", "127.0.0.1", 0, this.interfaceClass.getName())).addParameters(map);
            // 那么就从本地的代理调用
            this.invoker = REF_PROTOCOL.refer(this.interfaceClass, url);
            if (logger.isInfoEnabled()) {
                logger.info("Using injvm service " + this.interfaceClass.getName());
            }
        } else {
            // 如果不是本地调用,那么就是远程调用
            this.urls.clear();
            URL monitorUrl;
            // <dubbo:reference url = "dubbo:// ; dubbo://(可以配置多套参数)"> 点对点调用,是指直接在服务提供者和服务消费者之间建立直连的通信通道,绕过注册中心的调用方式。
            if (this.url != null && this.url.length() > 0) {
                // 处理url,然后去遍历
                // 构建url 然后添加到urls里面
                String[] us = CommonConstants.SEMICOLON_SPLIT_PATTERN.split(this.url);
                if (us != null && us.length > 0) {
                    String[] var11 = us;
                    int var14 = us.length;

                    for(int var17 = 0; var17 < var14; ++var17) {
                        String u = var11[var17];
                        URL url = URL.valueOf(u);
                        if (StringUtils.isEmpty(url.getPath())) {
                            url = url.setPath(this.interfaceName);
                        }

                        if (UrlUtils.isRegistry(url)) {
                            this.urls.add(url.addParameterAndEncoded("refer", StringUtils.toQueryString(map)));
                        } else {
                            this.urls.add(ClusterUtils.mergeUrl(url, map));
                        }
                    }
                }
            }
            // 如果我们发布的协议不是injvm协议,injvm是本地协议
            else if (!"injvm".equalsIgnoreCase(this.getProtocol())) {
                // 检查注册中心的配置
                this.checkRegistry();// 配置的dubbo.registry
                List<URL> us = ConfigValidationUtils.loadRegistries(this, false);
                if (CollectionUtils.isNotEmpty(us)) {
                    for(Iterator var3 = us.iterator(); var3.hasNext(); this.urls.add(u.addParameterAndEncoded("refer", StringUtils.toQueryString(map)))) {
                        u = (URL)var3.next();
                        monitorUrl = ConfigValidationUtils.loadMonitor(this, u);
                        if (monitorUrl != null) {
                            map.put("monitor", URL.encode(monitorUrl.toFullString()));
                        }
                    }
                }

                if (this.urls.isEmpty()) {
                    throw new IllegalStateException("No such any registry to reference " + this.interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
                }
            }

            if (this.urls.size() == 1) {
                this.invoker = REF_PROTOCOL.refer(this.interfaceClass, (URL)this.urls.get(0));
            } else {
                List<Invoker<?>> invokers = new ArrayList();
                URL registryURL = null;
                Iterator var16 = this.urls.iterator();

                while(var16.hasNext()) {
                    monitorUrl = (URL)var16.next();
                    invokers.add(REF_PROTOCOL.refer(this.interfaceClass, monitorUrl));
                    if (UrlUtils.isRegistry(monitorUrl)) {
                        registryURL = monitorUrl;
                    }
                }

                if (registryURL != null) {
                    u = registryURL.addParameterIfAbsent("cluster", "zone-aware");
                    this.invoker = CLUSTER.join(new StaticDirectory(u, invokers));
                    // 在这里之所以通过 StaticDirectory 去维护,是因为我们现在配置的 registry是静态的,主要是注册地址是写死的
                } else {
                    this.invoker = CLUSTER.join(new StaticDirectory(invokers));
                }
            }
        }       
java 复制代码
......

    		// 构建好了之后,通过这个方式,创建动态代理
            return PROXY_FACTORY.getProxy(this.invoker, ProtocolUtils.isGeneric(this.generic));
        }
    }

在上面这个方法中,有两个核心的代码需要关注,分别是。

  • REF_PROTOCOL.refer, 这个是生成invoker对象,之前我们说过,它是一个调用器,是dubbo中比较重要的领域对象,它在这里承担这服务调用的核心逻辑.
  • PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic)), 构建一个代理对象,代理客户端的请求。

REF_PROTOCOL.ref

我们先来分析refer方法。

REF_PROTOCOL是一个自适应扩展点,现在我们看到这个代码,应该是比较熟悉了。它会生成一个 Protocol$Adaptive的类,然后根据refer传递的的url参数来决定当前路由到哪个具体的协议处理器。

java 复制代码
Protocol REF_PROTOCOL =
ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

前面我们分析服务发布的时候,已经说过了这个过程,所以就跳过,直接进入到RegistryProtocol.refer中

RegistryProtocol.refer

RegistryProtocol这个类我们已经很熟悉了,服务注册和服务启动都是在这个类里面触发的。

现在我们又通过这个方法来获得一个inovker对象,那我们继续去分析refer里面做了什么事情。

这里面的代码逻辑比较简单

java 复制代码
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    // 获得注册中心的url地址
	// 此时,这里得到的是zookeeper://
        url = this.getRegistryUrl(url);
    //registryFactory,是一个自适应扩展点,RegistryFactory$Adaptive
	//定位到org.apache.dubbo.registry.RegistryFactory这个类可以知道,返回的实例是:ZookeeperRegistryFactory,并且是一个被RegistryFactoryWrapper包装的实例
        Registry registry = this.registryFactory.getRegistry(url);
        if (RegistryService.class.equals(type)) {
            return this.proxyFactory.getInvoker(registry, type, url);
        } else {
            Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded("refer"));
            String group = (String)qs.get("group");
            return group == null || group.length() <= 0 || CommonConstants.COMMA_SPLIT_PATTERN.split(group).length <= 1 && !"*".equals(group) ? this.doRefer(this.cluster, registry, type, url) : this.doRefer(this.getMergeableCluster(), registry, type, url);
        }
    }
doRefer

doRefer方法创建一个RegistryDirectory实例,然后生成服务者消费者连接,并向注册中心进行注册。注册完毕后,紧接着订阅providers、configurators、roters。

等节点下的数据。完成订阅后,RegistryDirectory会收到到这几个节点下的子节点信息。由于一个服务可能部署在多台服务器上,这样就会在providers产生多个节点。

这个时候就需要Cluster将多个服务节点合并为一个,并生成一个invoker。

java 复制代码
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
    	//初始化RegistryDirectory(注册中心的目录)
        RegistryDirectory<T> directory = new RegistryDirectory(type, url);
        directory.setRegistry(registry);// 注册中心
        directory.setProtocol(this.protocol);// 协议
    
        Map<String, String> parameters = new HashMap(directory.getConsumerUrl().getParameters());
    	//注册consumer://协议url
        URL subscribeUrl = new URL("consumer", (String)parameters.remove("register.ip"), 0, type.getName(), parameters);
        if (directory.isShouldRegister()) {
            //注册服务消费者的url地址
            directory.setRegisteredConsumerUrl(subscribeUrl);
            registry.register(directory.getRegisteredConsumerUrl());
        }

        directory.buildRouterChain(subscribeUrl);
        //进行订阅 订阅地址的变化
        //subscribe订阅信息消费url、通知监听、配置监听、订阅url
        //toSubscribeUrl:订阅信息:category、providers、configurators、routers
        directory.subscribe(toSubscribeUrl(subscribeUrl));
    //一个注册中心会存在多个服务提供者,所以在这里需要把多个服务提供者通过cluster.join合并成一个
        Invoker<T> invoker = cluster.join(directory);
    	// 只是初始化了一个RegistryDirectory,然后通过 Cluster.join 来返回一个Invoker对象
        List<RegistryProtocolListener> listeners = this.findRegistryProtocolListeners(url);
        if (CollectionUtils.isEmpty(listeners)) {
            return invoker;
        } else {
            
            //通过RegistryInvokerWrapper进行包装
            RegistryInvokerWrapper<T> registryInvokerWrapper = new RegistryInvokerWrapper(directory, cluster, invoker, subscribeUrl);
            Iterator var11 = listeners.iterator();

            while(var11.hasNext()) {
                RegistryProtocolListener listener = (RegistryProtocolListener)var11.next();
                listener.onRefer(this, registryInvokerWrapper);
            }

            return registryInvokerWrapper;
        }
    }

Cluster是什么?

我们只关注一下Invoker这个代理类的创建过程,其他的暂且不关心

java 复制代码
// 把directory放进去代表将来能从这里面拿到地址列表
Invoker invoker=cluster.join(directory)

cluster其实是在RegistryProtocol中通过set方法完成依赖注入的,并且,它还是一个被包装的。

java 复制代码
public void setCluster(Cluster cluster) {
	this.cluster = cluster;
}

所以,Cluster是一个被依赖注入的自适应扩展点,注入的对象实例是一个Cluster$Adaptive的动态代理类。

如下可以看到Cluster的定义

java 复制代码
@SPI(FailoverCluster.NAME)
public interface Cluster {

	@Adaptive
	<T> Invoker<T> join(Directory<T> directory) throws RpcException;
}
Cluster$Adaptive

在动态适配的类中会基于extName,选择一个合适的扩展点进行适配,由于默认情况下cluster:failover,所以

getExtension("failover")理论上应该返回FailOverCluster。但实际上,这里做了包装MockClusterWrapper(FailOverCluster)

java 复制代码
public class Cluster$Adaptive implements org.apache.dubbo.rpc.cluster.Cluster {
	public org.apache.dubbo.rpc.Invoker
join(org.apache.dubbo.rpc.cluster.Directory arg0) throws
org.apache.dubbo.rpc.RpcException {
        
		if (arg0 == null) throw new
IllegalArgumentException("org.apache.dubbo.rpc.cluster.Directory argument == null");
                         
		if (arg0.getUrl() == null) throw new
IllegalArgumentException("org.apache.dubbo.rpc.cluster.Directory argumentgetUrl() == null");
        
		org.apache.dubbo.common.URL url = arg0.getUrl();
        
		String extName = url.getParameter("cluster", "failover");
        
		if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.cluster.Cluster) name from url (" + url.toString() + ") use keys([cluster])");
        
		org.apache.dubbo.rpc.cluster.Cluster extension =
(org.apache.dubbo.rpc.cluster.Cluster)ExtensionLoader.getExtensionLoader(org.apa
che.dubbo.rpc.cluster.Cluster.class).getExtension(extName);
        
		return extension.join(arg0);
	}
}
cluster.join

所以再回到doRefer方法,下面这段代码, 实际是调用MockClusterWrapper(FailOverCluster.join)

java 复制代码
public class MockClusterWrapper implements Cluster {

	private Cluster cluster;
	
	public MockClusterWrapper(Cluster cluster) {
		this.cluster = cluster;
	}
	
	@Override
	public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
		return new MockClusterInvoker<T>(directory,
		this.cluster.join(directory));
	}
}

再调用AbstractCluster中的join方法

java 复制代码
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
	return buildClusterInterceptors(doJoin(directory),
directory.getUrl().getParameter(REFERENCE_INTERCEPTOR_KEY));
}

doJoin返回的是FailoverClusterInvoker。

buildClusterInterceptors从名字可以看出,这里是构建一个Cluster的拦截器。

java 复制代码
private <T> Invoker<T> buildClusterInterceptors(AbstractClusterInvoker<T> clusterInvoker, String key) {
        AbstractClusterInvoker<T> last = clusterInvoker;
    
    	//通过激活扩展点来获得ClusterInterceptor集合. 如果没有配置激活参数,默认会有一个ConsumerContextClusterInterceptor拦截器.
        List<ClusterInterceptor> interceptors = ExtensionLoader.getExtensionLoader(ClusterInterceptor.class).getActivateExtension(clusterInvoker.getUrl(), key);
    	//遍历拦截器集合,构建一个拦截器链.
        if (!interceptors.isEmpty()) {
            for(int i = interceptors.size() - 1; i >= 0; --i) {
                ClusterInterceptor interceptor = (ClusterInterceptor)interceptors.get(i);
                last = new AbstractCluster.InterceptorInvokerNode(clusterInvoker, interceptor, (AbstractClusterInvoker)last);
            }
        }

        return (Invoker)last;
    }
properties 复制代码
context=org.apache.dubbo.rpc.cluster.interceptor.ConsumerContextClusterIntercept
or
zone-aware=org.apache.dubbo.rpc.cluster.interceptor.ZoneAwareClusterInterceptor
Cluster.join总结

因此 Cluster.join,实际上是获得一个Invoker对象,这个Invoker实现了Directory的包装,并且配置了拦截器。至于它是干嘛的,我们后续再分析。

RegistryDirectory 订阅

大家还记得在RegistryProtocol中,调用doRefer这个方法吗?

这个doRefer方法中,构建了一个RegistryDirectory,之前我们不明白Directory的含义,所以暂时没有管,但是它的作用很重要,它负责动态维护服务提供者列表。

java 复制代码
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
    	//初始化RegistryDirectory(注册中心的目录)
        RegistryDirectory<T> directory = new RegistryDirectory(type, url);
        directory.setRegistry(registry);// 注册中心
        directory.setProtocol(this.protocol);// 协议
    
        Map<String, String> parameters = new HashMap(directory.getConsumerUrl().getParameters());
    	//注册consumer://协议url
        URL subscribeUrl = new URL("consumer", (String)parameters.remove("register.ip"), 0, type.getName(), parameters);
        if (directory.isShouldRegister()) {
            //注册服务消费者的url地址
            directory.setRegisteredConsumerUrl(subscribeUrl);
            registry.register(directory.getRegisteredConsumerUrl());
        }

        directory.buildRouterChain(subscribeUrl);
        //进行订阅 订阅地址的变化
        //subscribe订阅信息消费url、通知监听、配置监听、订阅url
        //toSubscribeUrl:订阅信息:category、providers、configurators、routers
        directory.subscribe(toSubscribeUrl(subscribeUrl));
    
    //一个注册中心会存在多个服务提供者,所以在这里需要把多个服务提供者通过cluster.join合并成一个
        Invoker<T> invoker = cluster.join(directory);
    	// 只是初始化了一个RegistryDirectory,然后通过 Cluster.join 来返回一个Invoker对象
        List<RegistryProtocolListener> listeners = this.findRegistryProtocolListeners(url);
        if (CollectionUtils.isEmpty(listeners)) {
            return invoker;
        } else {
            
            //通过RegistryInvokerWrapper进行包装
            RegistryInvokerWrapper<T> registryInvokerWrapper = new RegistryInvokerWrapper(directory, cluster, invoker, subscribeUrl);
            Iterator var11 = listeners.iterator();

            while(var11.hasNext()) {
                RegistryProtocolListener listener = (RegistryProtocolListener)var11.next();
                listener.onRefer(this, registryInvokerWrapper);
            }

            return registryInvokerWrapper;
        }
    }
服务目录

RegistryDirectory是Dubbo中的服务目录,从名字上来看,也比较容易理解,服务目录中存储了一些和服务提供者有关的信息,通过服务目录,服务消费者可获取到服务提供者的信息,比如 ip、端口、服务协议等。通过这些信息,服务消费者就可通过 Netty 等客户端进行远程调用。

所以,再回忆一下之前我们通过Cluster.join去构建Invoker时,传递了一个directory进去,因为Invoker是一个调用器,在发起远程调用时,必然需要从directory中去拿到所有的服务提供者列表,然后再通过负载均衡机制来发起请求。

RegistryDirectory.subscribe

订阅注册中心指定节点的变化,如果发生变化,则通知到RegistryDirectory。Directory其实和服务的注册以及服务的发现有非常大的关联.

java 复制代码
public void subscribe(URL url) {
    	// 对消费者的信息进行监听和订阅
        this.setConsumerUrl(url);
    	// 当注册中心的事件发生变化的时候,会有一个方法接收事件变化的通知,从而去更改 RegistryDirectory 里面的invoker
        CONSUMER_CONFIGURATION_LISTENER.addNotifyListener(this);
        this.serviceConfigurationListener = new RegistryDirectory.ReferenceConfigurationListener(this, url);
        this.registry.subscribe(url, this);
    } 

此时,registry我们知道,它是ListenerRegistryWrapper(ZookeeperRegsistry)对象,我们先不管包装类,直接进入到ZookeeperRegistry这个类中

FailbackRegistry.subscribe

registry.subscribe这个方法,最终会调用FailbackRegistry.subscrbe

其中入参, listener为RegistryDirectory,后续要用到failbackRegistry这个类,从名字就可以看出,它的主要作用就是实现具有故障恢复功能的服务订阅机制,简单来说就是如果在订阅服务注册中心时出现异常,会触发重试机制。

java 复制代码
 // 通过前面 动态扩展点 ZookeeperRegistryFactory 说明后面会进入到ZookeeperRegistry ,但是没有    subscribe 所以要去看其父类的实现 FailbackRegistry
    
    // 下面进入的就是zk的订阅
public void subscribe(URL url, NotifyListener listener) {
        super.subscribe(url, listener);
        this.removeFailedSubscribed(url, listener);

        try {
            // 进入到 ZookeeperRegistry 里的 doSubscribe方法
            // 发送订阅请求到服务端
            this.doSubscribe(url, listener);
        } 
    		.....
                // 记录失败的注册订阅请求,根据规则进行重试
            this.addFailedSubscribed(url, listener);
        }

    }
ZookeeperRegistry.doSubscribe

这个方法是订阅,逻辑实现比较多,可以分两段来看,这里的实现把所有Service层发起的订阅以及指定的Service层发起的订阅分开处理。所有Service层类似于监控中心发起的订阅。指定的Service层发起的订阅可以看作是服务消费者的订阅。我们只需要关心指定service层发起的订阅即可

java 复制代码
public void doSubscribe(final URL url, final NotifyListener listener) {
        try {
            //针对所有service层发起的定于
            if ("*".equals(url.getServiceInterface())) {
                ......
            } else {
                //针对指定的服务地址发起订阅
                //toCategoriesPath 会返回三个结果,分别是/providers、/configurations、/routers。 也就是服务启动时需要监听这三个节点下的数据变化
				List<URL> urls = new ArrayList<>();
                
				for (String path : toCategoriesPath(url)) {
					//构建一个listeners集合,其中key是NotifyListener,其中前面我们知道RegistryDirectory实现了这个接口,所以这里的key应该是RegistryDirectory
					//value表示针对这个RegistryDirectory注册的子节点监听。
					ConcurrentMap<NotifyListener, ChildListener> listeners =
zkListeners.computeIfAbsent(url, k -> new ConcurrentHashMap<>());
                    
					ChildListener zkListener = listeners.computeIfAbsent(listener, k
-> (parentPath, currentChilds) -> ZookeeperRegistry.this.notify(url, k,
toUrlsWithEmpty(url, parentPath, currentChilds)));
                    
					zkClient.create(path, false);//创建一个/providers or/configurators、/routers节点
                    
					List<String> children = zkClient.addChildListener(path,zkListener);
                    
					if (children != null) {
                        urls.addAll(toUrlsWithEmpty(url, path, children));
					}
				}
				//调用notify方法触发监听
				notify(url, listener, urls);
	
FailbackRegistry.notify

调用FailbackRegistry.notify, 对参数进行判断。 然后调用AbstractRegistry.notify方法

java 复制代码
protected void notify(URL url, NotifyListener listener, List<URL> urls) {
        ......
            try {
                // 其实是 ZookeeperRegistry实例
                this.doNotify(url, listener, urls);
  ......
    }
    
protected void doNotify(URL url, NotifyListener listener, List<URL> urls) {
        super.notify(url, listener, urls); // 其调用父类 AbstractRegistry
    }
AbstractRegistry.notify
java 复制代码
protected void notify(URL url, NotifyListener listener, List<URL> urls) {
        ......
            Map<String, List<URL>> result = new HashMap();
            Iterator var5 = urls.iterator();

            while(var5.hasNext()) {
                URL u = (URL)var5.next();
                if (UrlUtils.isMatch(url, u)) {
                    String category = u.getParameter("category", "providers");
                    List<URL> categoryList = (List)result.computeIfAbsent(category, (k) -> {
                        return new ArrayList();
                    });
                    
                    categoryList.add(u);
                }
            }

            if (result.size() != 0) {
                Map<String, List<URL>> categoryNotified = (Map)this.notified.computeIfAbsent(url, (ux) -> {
                    return new ConcurrentHashMap();
                });
                Iterator var11 = result.entrySet().iterator();

                while(var11.hasNext()) {
                    Entry<String, List<URL>> entry = (Entry)var11.next();
                    String category = (String)entry.getKey();
                    List<URL> categoryList = (List)entry.getValue();
                    categoryNotified.put(category, categoryList);
                    //通过listener监听categoryList
                    // 前面的监听主要是根据urls进行的,下面的监听就相当于根据 具体的url进行的。
                    listener.notify(categoryList);
                    // 将注册中心的地址保存在本地缓存
                    //保存到本地文件中,作为服务地址的缓存信息
                    this.saveProperties(url);
                }

            }
        }
    }

上述代码中,我们重点关注 listener.notify ,它会触发一个事件通知,消费端的listener是最开始传递过来的RegistryDirectory,所以这里会触发RegistryDirectory.notify

RegistryDirectory.notify

看到这个代码,大家是不是更进一步理解了,服务地址发生变化和更新时,调用Directory.notify来更新,那么是不是意味着更新后的信息,会同步到Directory中呢?

带着这个思考,我们继续去分析代码。

java 复制代码
public synchronized void notify(List<URL> urls) {
    //对数据进行过滤。
        Map<String, List<URL>> categoryUrls = 		 
      (Map)urls.stream().
      filter(Objects::nonNull).
      filter(this::isValidCategory).
      filter(this::isNotCompatibleFor26x).
      collect(Collectors.groupingBy(this::judgeCategory));
    
    //假设当前进来的通知是 providers节点
	//判断configurator是否为空,这个节点下的配置,是在dubbo-admin控制台上修改配置时,会先创建一个配置节点到这个路径下,注册中心收到这个变化时会通知服务消费者,服务消费者会根据新的配置重新构建Invoker

        List<URL> configuratorURLs = (List)categoryUrls.getOrDefault("configurators", Collections.emptyList());
    
        this.configurators = (List)Configurator.toConfigurators(configuratorURLs).orElse(this.configurators);
    	//判断路由规则配置是否为空,如果不为空,同样将路由规则添加到url中。
        List<URL> routerURLs = (List)categoryUrls.getOrDefault("routers", Collections.emptyList());
		this.toRouters(routerURLs).ifPresent(this::addRouters);
    	// providers 得到服务提供者的地址列表
        List<URL> providerURLs = (List)categoryUrls.getOrDefault("providers", Collections.emptyList());
    	// 前面就是针对url进行改造
    
        ......
		// 这个方法就很清楚了,根据监听后修改的url进行刷新重载Invoker
        this.refreshOverrideAndInvoker(providerURLs);
    }
refreshOverrideAndInvoker
java 复制代码
private void refreshOverrideAndInvoker(List<URL> urls) {
    	//付给DirectoryUrl
        this.overrideDirectoryUrl();
    	//刷新invoker
        this.refreshInvoker(urls);
    }
  • 逐个调用注册中心里面的配置,覆盖原来的url,组成最新的url 放入overrideDirectoryUrl 存储,此时我们没有在dubbo-admin中修改任何配置,所以这里没必要去分析
  • 根据 provider urls,重新刷新Invoker
refreshInvoker

从名字可以看到,这里是刷新invoker。怎么理解呢?

当注册中心的服务地址发生变化时,会触发更新。而更新之后并不是直接把url地址存储到内存,而是把url转化为invoker进行存储,这个invoker是作为通信的调用器来构建的领域对象,所以如果地址发生变化,那么需要把老的invoker销毁,然后用心的invoker替代。

java 复制代码
// 刷新Invoker
private void refreshInvoker(List<URL> invokerUrls) {
        Assert.notNull(invokerUrls, "invokerUrls should not be null");
    // 前面介绍过,如果发现注册中心不可用的时候,依然可以调用,因为能拿到本地缓存文件里面的注册中心协议的地址然后调用
    //如果只有一个服务提供者,并且如果是空协议,那么这个时候直接返回进制访问,并且销毁所有的invokers
        if (((List)invokerUrls).size() == 1 && ((List)invokerUrls).get(0) != null && "empty".equals(((URL)((List)invokerUrls).get(0)).getProtocol())) {
            this.forbidden = true;
            this.invokers = Collections.emptyList();
            this.routerChain.setInvokers(this.invokers);
            // 如果服务提供者只有一个并且是空协议,会禁止调用
            // 销毁所有的invoker
            this.destroyAllInvokers();
        } else {
            
            this.forbidden = false;
            //获取老的invoker集合
            Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap;
            if (invokerUrls == Collections.emptyList()) {
                invokerUrls = new ArrayList();
            }

            if (((List)invokerUrls).isEmpty() && this.cachedInvokerUrls != null) {
                ((List)invokerUrls).addAll(this.cachedInvokerUrls);
            } else {
                this.cachedInvokerUrls = new HashSet();
                this.cachedInvokerUrls.addAll((Collection)invokerUrls);
            }

            if (((List)invokerUrls).isEmpty()) {
                return;
            }

            // 构建一个newnivoker
            // 转换具体协议的invoker信息
            //把invokerUrls转化为InvokerMap
            Map<String, Invoker<T>> newUrlInvokerMap = this.toInvokers((List)invokerUrls);
             if (CollectionUtils.isEmptyMap(newUrlInvokerMap)) {
                logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :" + ((List)invokerUrls).size() + ", invoker.size :0. urls :" + invokerUrls.toString()));
                return;
            }

            List<Invoker<T>> newInvokers = Collections.unmodifiableList(new ArrayList(newUrlInvokerMap.values()));
            this.routerChain.setInvokers(newInvokers);
            this.invokers = this.multiGroup ? this.toMergeInvokerList(newInvokers) : newInvokers;
            // 然后赋值到这个里面
            //旧的url 是否在新map里面存在,不存在,就是销毁url对应的Invoker
            this.urlInvokerMap = newUrlInvokerMap;

            try {
                this.destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap);
            } catch (Exception var6) {
                logger.warn("destroyUnusedInvokers error. ", var6);
            }
        }

    }
toInvokers

这个方法中有比较长的判断和处理逻辑,我们只需要关心invoker是什么时候初始化的就行。这里用到了protocol.refer来构建了一个invoker

invoker = new InvokerDelegate<>(protocol.refer(serviceType, url), url,providerUrl);

构建完成之后,会保存在 Map> urlInvokerMap 这个集合中

java 复制代码
 
    private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
        Map<String, Invoker<T>> newUrlInvokerMap = new HashMap();
        .............

                                    if (enabled) {
                                        invoker = new RegistryDirectory.InvokerDelegate(this.protocol.refer(this.serviceType, url), url, providerUrl);
                                    }
                                } catch (Throwable var13) {
                                    logger.error("Failed to refer invoker for interface:" + this.serviceType + ",url:(" + url + ")" + var13.getMessage(), var13);
                                }

                                if (invoker != null) {
                                    newUrlInvokerMap.put(key, invoker);
                                }
                            } else {
                                newUrlInvokerMap.put(key, invoker);
                            }
                        }
                    }
                }
            }
        } else {
            return newUrlInvokerMap;
        }
    }    
总结

简单总结一下,RegistryDirectory.subscribe,其实总的来说就相当于做了两件事

  • 定于指定注册中心的以下三个路径的子节点变更事件
bash 复制代码
/dubbo/org.apache.dubbo.demo.DemoService/providers
、/dubbo/org.apache.dubbo.demo.DemoService/configurators、/dubbo/org.apache.
dubbo.demo.DemoService/routers
  • 触发时间变更之后,把变更的节点信息转化为Invoker

Protocol.refer

咱们继续往下看,在toInvokers这个方法中,invoker是通过 protocol.refer来构建的。那么我们再来分析一下refer里面做了什么?

java 复制代码
protocol.refer(serviceType, url), url, providerUrl)

不过在往下分析之前,我们先来猜想一下它会做什么?根据在这段逻辑所处的位置和它在整个dubbo中的作用,我们应该可以猜测出。

  • 这个invoker应该和通信有关系
  • 那么这里应该会建立一个网络通信的连接

根据猜想,我们去看一下它的实现,首先protocol,是被依赖注入进来的自适应扩展点Protocol$Adaptive. ,此时传进去的参数,此时url对应的地址应该是dubbo://开头的协议地址,所以最终获得的是通过包装之后的DubboProtocol对象。

less 复制代码
QosProtocolWrapper(ProtocolFilterWrapper(ProtocolListenerWrapper(DubboProtocol)))

其中,在Qos这边,会启动Qosserver、在FilterWrapper中,会构建一个过滤器链,其中包含访问日志过滤、访问限制过滤等等,这个最终在调用的时候会通过一条过滤器链路对请求进行处理。

AbstractProtocol.refer

DubboProtocol中没有refer方法,而是调用父类的refer。

java 复制代码
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
	return new AsyncToSyncInvoker<>(protocolBindingRefer(type, url));
}
protocolBindingRefer
  • 优化序列化
  • 构建DubboInvoker

在构建DubboInvoker时,会构建一个ExchangeClient,通过getClients(url)方法,这里基本可以猜到到是服务的通信建立

java 复制代码
@Override
public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws
RpcException {

	optimizeSerialization(url);
	
	// create rpc invoker.
	DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url,getClients(url), invokers);
	invokers.add(invoker);
	return invoker;
}
getClients

这里面是获得客户端连接的方法

  • 判断是否为共享连接,默认是共享同一个连接进行通信
  • 是否配置了多个连接通道 connections,默认只有一个共享连接
java 复制代码
private ExchangeClient[] getClients(URL url) {
        boolean useShareConnect = false;
        int connections = url.getParameter("connections", 0);
        List<ReferenceCountExchangeClient> shareClients = null;
    	// 如果没有配置的情况下,默认是采用共享连接,否则,就是针对一个服务提供一个连接。
		//所谓共享连接,实际上就是
        if (connections == 0) {
            useShareConnect = true;
            String shareConnectionsStr = url.getParameter("shareconnections", (String)null);
            connections = Integer.parseInt(StringUtils.isBlank(shareConnectionsStr) ? ConfigUtils.getProperty("shareconnections", "1") : shareConnectionsStr);
            //返回共享连接
            shareClients = this.getSharedClient(url, connections);
        }
		//从共享连接中获得客户端连接进行返回
        ExchangeClient[] clients = new ExchangeClient[connections];

        for(int i = 0; i < clients.length; ++i) {
            if (useShareConnect) {
                clients[i] = (ExchangeClient)shareClients.get(i);
            } else { //如果不是使用共享连接,则初始化一个新的客户端连接进行返回
                clients[i] = this.initClient(url);
            }
        }

        return clients;
    }
getSharedClient
java 复制代码
private List<ReferenceCountExchangeClient> getSharedClient(URL url, int connectNum) {
        String key = url.getAddress();
        List<ReferenceCountExchangeClient> clients = (List)this.referenceClientMap.get(key);
    	// //检查当前的key检查连接是否已经创建过并且可用,如果是,则直接返回并且增加连接的个数的统计
        if (this.checkClientCanUse(clients)) {
            this.batchClientRefIncr(clients);
            return clients;
        } else {
            //如果连接已经关闭或者连接没有创建过
            this.locks.putIfAbsent(key, new Object());
            synchronized(this.locks.get(key)) {
                clients = (List)this.referenceClientMap.get(key);
                // 双重检查
                if (this.checkClientCanUse(clients)) {
                    this.batchClientRefIncr(clients);
                    return clients;
                } else {
                    // 连接数必须大于等于1
                    connectNum = Math.max(connectNum, 1);
                    //如果当前消费者还没有和服务端产生连接,则初始化
                    if (CollectionUtils.isEmpty(clients)) {
                        clients = this.buildReferenceCountExchangeClientList(url, connectNum);
                        this.referenceClientMap.put(key, clients);
                    } else {//如果clients不为空,则从clients数组中进行遍历

                        for(int i = 0; i < clients.size(); ++i) {
                            ReferenceCountExchangeClient referenceCountExchangeClient = (ReferenceCountExchangeClient)clients.get(i);
                            // 如果在集合中存在一个连接但是这个连接处于closed状态,则重新构建一个进行替换
                            if (referenceCountExchangeClient != null && !referenceCountExchangeClient.isClosed()) {
                                //增加个数
                                referenceCountExchangeClient.incrementAndGetCount();
                            } else {
                                clients.set(i, this.buildReferenceCountExchangeClient(url));
                            }
                        }
                    }

                    this.locks.remove(key);
                    return clients;
                }
            }
        }
    }
buildReferenceCountExchangeClient

开始初始化客户端连接.

也就是说,dubbo消费者在启动的时候,先从注册中心上加载最新的服务提供者地址,然后转化成invoker,但是转化的时候,也会同步去建立一个连接。

而这个连接默认采用的是共享连接,因此就会存在对于同一个服务提供者,假设客户端依赖了多个 @DubboReference,那么这个时候这些服务的引用会使用同一个连接进行通信。(举个例子来说明,假设有两个服务提供者A和B,它们都提供了不同的服务。现在在Dubbo消费者中,我们分别使用了@DubboReference注解来引用A和B的服务。默认情况下,A和B的服务引用会使用同一个连接与对应的服务提供者进行通信。)

这种方式可以有效地减少连接数,提高资源利用率,但也可能会带来一些问题,比如如果某个服务出现了连接问题,可能会影响到其他依赖该服务的引用。因此,开发者在使用Dubbo时,需要根据实际情况来选择合适的连接方式。

java 复制代码
private ReferenceCountExchangeClient buildReferenceCountExchangeClient(URL url)
{
	ExchangeClient exchangeClient = initClient(url);
	return new ReferenceCountExchangeClient(exchangeClient);
}
initClient

终于进入到初始化客户端连接的方法了,猜测应该是根据url中配置的参数进行远程通信的构建

java 复制代码
private ExchangeClient initClient(URL url) {
    	// 获取客户端通信类型,默认是netty
        String str = url.getParameter("client", url.getParameter("server", "netty"));
    	//获取编码解码类型,默认是Dubbo自定义类型
        url = url.addParameter("codec", "dubbo");
        // 开启心跳并设置心跳间隔,默认60s
        url = url.addParameterIfAbsent("heartbeat", String.valueOf(60000));
    
    	// 如果没有找到指定类型的通信扩展点,则抛出异常
        if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
            throw new RpcException("Unsupported client type: " + str + ", supported client type is " + StringUtils.join(ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(), " "));
        } else {
            try {
                Object client;
                // 是否延迟建立连接
                if (url.getParameter("lazy", false)) {
                    client = new LazyConnectExchangeClient(url, this.requestHandler);
                } else {//默认是直接在启动时建立连接
                    client = Exchangers.connect(url, this.requestHandler);
                }

                return (ExchangeClient)client;
            } catch (RemotingException var5) {
                throw new RpcException("Fail to create remoting client for service(" + url + "): " + var5.getMessage(), var5);
            }
        }
    }
Exchangers.connect

创建一个客户端连接

java 复制代码
public static ExchangeClient connect(URL url, ExchangeHandler handler) throws
RemotingException {
	if (url == null) {
		throw new IllegalArgumentException("url == null");
	}
	if (handler == null) {
		throw new IllegalArgumentException("handler == null");
	}
	url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
	return getExchanger(url).connect(url, handler);
}

public static Exchanger getExchanger(URL url) {
        String type = url.getParameter("exchanger", "header");
        return getExchanger(type);
    }
	// 动态扩展点
    public static Exchanger getExchanger(String type) {
        return (Exchanger)ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type);
    }
HeaderExchange.connect
java 复制代码
@Override
public ExchangeClient connect(URL url, ExchangeHandler handler) throws
RemotingException {
	return new HeaderExchangeClient(Transporters.connect(url, new
DecodeHandler(new HeaderExchangeHandler(handler))), true);
}

同样Transporters 也是动态扩展点

NettyTransport.connect

使用netty构建了一个客户端连接

java 复制代码
@Override
public Client connect(URL url, ChannelHandler handler) throws RemotingException
{
	return new NettyClient(url, handler);
}
总结

我们讲到了 RegistryProtocol.refer 过程中有一个关键步骤,即在监听到服务提供者url时触发RegistryDirectory.notify() 方法。

RegistryDirectory.notify() 方法调用 refreshInvoker() 方法将服务提供者urls转换为对应的 远程invoker ,最终调用到 DubboProtocol.refer() 方法生成对应的 DubboInvoker 。

DubboInvoker 的构造方法中有一项入参 ExchangeClient\[\] clients ,即对应本文要讲的网络客户端 Client 。DubboInvoker就是通过调用 client.request() 方法完成网络通信的请求发送和响应接收功能。

Client 的具体生成过程就是通过 DubboProtocol 的 initClient(URL url) 方法创建了一个HeaderExchangeClient 。

ReferenceConfig.createProxy

返回了Invoker之后,再回到createProxy这段代码中,这里最终会调用一个getProxy来返回一个动态代理对象。

并且这里把invoker作为参数传递进去,上面我们知道invoker这个对象的构建过程,实际上封装了一个directory在invoker中。

java 复制代码
return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));

而这里的proxyFactory是一个自适应扩展点,它会根据url中携带的proxy参数来决定选择哪种动态代理技术来构建动态代理对象,默认是javassist

java 复制代码
import org.apache.dubbo.common.extension.ExtensionLoader;
public class ProxyFactory$Adaptive implements org.apache.dubbo.rpc.ProxyFactory
{
	public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0) throws
org.apache.dubbo.rpc.RpcException {
		if (arg0 == null) throw new
IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");

		if (arg0.getUrl() == null) throw new
IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() ==null");

		org.apache.dubbo.common.URL url = arg0.getUrl();
		
		String extName = url.getParameter("proxy", "javassist");
		
		if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString()+ ") use keys([proxy])");
		
		org.apache.dubbo.rpc.ProxyFactory extension =
(org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache
.dubbo.rpc.ProxyFactory.class).getExtension(extName);

		return extension.getProxy(arg0);
}

	public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0, boolean
arg1) throws org.apache.dubbo.rpc.RpcException {

		if (arg0 == null) throw new
IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");

		if (arg0.getUrl() == null) throw new
IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() ==null");

		org.apache.dubbo.common.URL url = arg0.getUrl();
	
		String extName = url.getParameter("proxy", "javassist");
		if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString()+ ") use keys([proxy])");
		
		org.apache.dubbo.rpc.ProxyFactory extension =
(org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache
.dubbo.rpc.ProxyFactory.class).getExtension(extName);

		return extension.getProxy(arg0, arg1);
	}
	
	public org.apache.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0,
java.lang.Class arg1, org.apache.dubbo.common.URL arg2) throws
org.apache.dubbo.rpc.RpcException {

		if (arg2 == null) throw new IllegalArgumentException("url == null");
		
		org.apache.dubbo.common.URL url = arg2;
		
		String extName = url.getParameter("proxy", "javassist");
		
		if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString()+ ") use keys([proxy])");
		
		org.apache.dubbo.rpc.ProxyFactory extension =
(org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache
.dubbo.rpc.ProxyFactory.class).getExtension(extName);

		return extension.getInvoker(arg0, arg1, arg2);
	}
}
JavassistProxyFactory.getProxy

通过这个方法生成了一个动态代理类,并且对invoker再做了一层处理,InvokerInvocationHandler。意味着后续发起服务调用的时候,会由InvokerInvocationHandler来进行处理。

java 复制代码
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
	return (T) Proxy.getProxy(interfaces).newInstance(new
InvokerInvocationHandler(invoker));
}
proxy.getProxy

在proxy.getProxy这个方法中会生成一个动态代理类,通过debug的形式可以看到动态代理类的原貌在getProxy这个方法位置加一个断点

java 复制代码
proxy = (Proxy) pc.newInstance();

然后在debug窗口,找到ccp这个变量 -> mMethods。

从这个sayHello方法可以看出,我们通过

@Reference注入的一个对象实例本质上就是一个动态代理类,通过调用这个类中的方法,会触发handler.invoke(), 而这个handler就是InvokerInvocationHandler

以上的步骤都是属于消费者启动的时候所做的动作

相关推荐
爱勇宝12 小时前
第 1 章:别把“需求文档”当成真正的需求
前端·后端·程序员
IT_陈寒17 小时前
闭包陷阱让我加了两天班,JavaScript你真行
前端·人工智能·后端
易协同低代码18 小时前
通达OA核心类库TD类深度解析
后端
Gopher_HBo18 小时前
Go语言学习笔记(十八)Gin处理Session
后端
谭光志19 小时前
工具塞满上下文窗口怎么办?深度拆解 AI Agent Tool Search 按需加载实现原理
前端·后端·ai编程
她说..19 小时前
Java 默认值设置方式
java·开发语言·后端·springboot
foggyprojects19 小时前
从0开始,一句话启动AI DataAgent
后端·数据分析·ai编程
郡杰19 小时前
一些基础和问题解决
后端
陈随易19 小时前
前端项目部署只要30秒
前端·后端·程序员
YIAN19 小时前
从零手写文件读取 MCP 服务:一文吃透 Model Context Protocol 全链路通信原理
前端·后端·mcp