Dubbo 3.x源码(21)—Dubbo服务引用源码(4)

基于Dubbo 3.1,详细介绍了Dubbo服务的发布与引用的源码。

此前我们学习了createInvokerForRemote方法中的Wrapper有哪些以及作用,接下来我们将会的学习真正的本地、应用级别、接口级别的Protocol的引入逻辑,以及创建Proxy服务接口代理对象的逻辑。

Dubbo 3.x服务引用源码:

  1. Dubbo 3.x源码(11)—Dubbo服务的发布与引用的入口
  2. Dubbo 3.x源码(18)—Dubbo服务引用源码(1)
  3. Dubbo 3.x源码(19)—Dubbo服务引用源码(2)
  4. Dubbo 3.x源码(20)—Dubbo服务引用源码(3)
  5. Dubbo 3.x源码(21)—Dubbo服务引用源码(4)

Dubbo 3.x服务发布源码:

  1. Dubbo 3.x源码(11)—Dubbo服务的发布与引用的入口
  2. Dubbo 3.x源码(12)—Dubbo服务发布导出源码(1)
  3. Dubbo 3.x源码(13)—Dubbo服务发布导出源码(2)
  4. Dubbo 3.x源码(14)—Dubbo服务发布导出源码(3)
  5. Dubbo 3.x源码(15)—Dubbo服务发布导出源码(4)
  6. Dubbo 3.x源码(16)—Dubbo服务发布导出源码(5)
  7. Dubbo 3.x源码(17)—Dubbo服务发布导出源码(6)

1 InjvmProtocol本地引入协议

这是本地引入的协议实现,其代码很简单,就是构建一个InjvmInvoker对象实例,同时内部保存着本地导出的服务缓存exporterMap。

本地引入并没有涉及到注册中心以及网络服务器客户端。

/**
 * AbstractProtocol的方法
 */
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    //该方法由子类实现
    return protocolBindingRefer(type, url);
}
/**
 * InjvmProtocol的方法
 */
@Override
public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException {
    //返回一个InjvmInvoker实例,同时内部保存着本地导出的服务缓存exporterMap
    return new InjvmInvoker<T>(serviceType, url, url.getServiceKey(), exporterMap);
}

2 RegistryProtocol应用级远程服务引入协议

应用级服务远程协议以service-discovery-registry开头,其对应的Protocol实现就是RegistryProtocol。RegistryProtocol的refer方法大概步骤为:

  1. 调用getRegistryUrl方法获取注册中心协议url,如果是RegistryProtocol的实现则将url更换为应用级服务发现协议url,如果是InterfaceCompatibleRegistryProtocol的实现则尝试url更换为真实注册中心协议url。
  2. 调用getRegistry方法获取注册中心操作类Registry。基于Dubbo SPI机制根据url的协议加载具体的注册中心操作类,service-discovery-registry对应着ServiceDiscoveryRegistry,zookeeper对应着ZookeeperRegistry。
  3. 如果服务类型为RegistryService,那么通过proxyFactory基于registry实例获取Invoker。
  4. 获取group属性,即想要引用的服务分组。如果配置了分组,调用doRefer方法基于MergeableCluster继续引用服务,否则调用doRefer方法基于基于服务参数指定的cluster继续引用服务。
/**
 * RegistryProtocol的方法
 *
 * @param type 服务类型
 * @param url  远程注册中心服务协议地址,以service-discovery-registry或者registry作为协议
 */
@Override
@SuppressWarnings("unchecked")
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    /*
     * 1 获取注册中心协议url。
     */
    url = getRegistryUrl(url);
    /*
     * 2 获取注册中心操作类Registry
     * 基于Dubbo SPI机制根据url的协议加载具体的注册中心操作类,service-discovery-registry对应着ServiceDiscoveryRegistry
     * zookeeper对应着ZookeeperRegistry
     */
    Registry registry = getRegistry(url);
    //如果服务类型为RegistryService
    if (RegistryService.class.equals(type)) {
        //基于registry实例获取Invoker
        return proxyFactory.getInvoker((T) registry, type, url);
    }

    // group="a,b" or group="*"
    //获取服务引用属性map
    Map<String, String> qs = (Map<String, String>) url.getAttribute(REFER_KEY);
    //获取group,即想要引用的服务分组
    String group = qs.get(GROUP_KEY);
    //如果配置了分组
    if (StringUtils.isNotEmpty(group)) {
        if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
            //调用doRefer方法基于MergeableCluster继续引用服务
            return doRefer(Cluster.getCluster(url.getScopeModel(), MergeableCluster.NAME), registry, type, url, qs);
        }
    }
    //基于Dubbo SPI获取Cluster实现,Cluster用于支持集群容错策略
    Cluster cluster = Cluster.getCluster(url.getScopeModel(), qs.get(CLUSTER_KEY));
    //调用doRefer方法基于基于服务参数指定的cluster继续引用服务
    return doRefer(cluster, registry, type, url, qs);
}

2.1 getRegistryUrl获取注册中心url

该方法会判断当前协议如果已经是应用级服务发现协议,即service-discovery-registry,那么直接返回原协议。否则,url添加registry={protocol}参数,并且协议设置为service-discovery-registry协议,即应用级服务发现协议。

即RegistryProtocol#getRegistryUrl方法主要是将注册中心协议url更换为应用级服务发现协议url。

/**
 * RegistryProtocol的方法
 *
 * @param url 远程服务发现协议url
 * @return 注册中心协议url
 */
protected URL getRegistryUrl(URL url) {
    //如果是应用级服务发现协议,即service-discovery-registry,那么直接返回原协议
    if (SERVICE_REGISTRY_PROTOCOL.equals(url.getProtocol())) {
        return url;
    }
    //否则,url添加registry={protocol}参数,并且协议设置为service-discovery-registry协议
    return url.addParameter(REGISTRY_KEY, url.getProtocol()).setProtocol(SERVICE_REGISTRY_PROTOCOL);
}

2.2 getCluster获取集群对象

//基于Dubbo SPI获取Cluster实现,Cluster用于支持集群容错策略
Cluster cluster = Cluster.getCluster(url.getScopeModel(), qs.get(CLUSTER_KEY));

集群对象Cluster,实际上Cluster不仅仅有容错功能,因为其还代表着Dubbo十层架构中的集群层,因此,集群路由、负载均衡、发起RPC调用、失败重试、服务降级都是在该层实现的。但是这些功能不都是由Cluster接口实现的,而是由集群层的其他核心接口实现,例如Directory 、 Router 、 LoadBalance。

Cluster通过基于Dubbo SPI机制获取,同时还会被wrapper包装,可以通过属性cluster设置集群方式,可选:failover/failfast/failsafe/failback/forking,如不设置默认failover,即FailoverCluster。


static Cluster getCluster(ScopeModel scopeModel, String name) {
    //允许wrapper
    return getCluster(scopeModel, name, true);
}

static Cluster getCluster(ScopeModel scopeModel, String name, boolean wrap) {
    //如果name为空,那么默认failover
    if (StringUtils.isEmpty(name)) {
        name = Cluster.DEFAULT;
    }
    //基于Dubbo SPI获取,可能会经过wrapper
    return ScopeModelUtil.getApplicationModel(scopeModel).getExtensionLoader(Cluster.class).getExtension(name, wrap);
}

由于允许wrapper包装,那么实际返回的Cluster并不是FailoverCluster,而是MockClusterWrapper。
image.png

2.3 doRefer执行远程引用服务

该方法执行远程引用服务,RegistryProtocol和InterfaceCompatibleRegistryProtocol对于该方法的大概方法骨架是一样的,只是某些方法实现不同。

大概步骤为:

  1. 根据消费方的服务引用配置构建服务消费invoker,协议为服务消费配置的protocol属性,表示只调用指定协议的服务提供方,其它协议忽略,默认值consumer。随后将当前consumerUrl存入注册中心协议url的属性缓存中,key为CONSUMER_URL。
  2. 调用getMigrationInvoker方法获取可迁移Invoker。应用级服务引用RegistryProtocol的getMigrationInvoker方法将会返回ServiceDiscoveryMigrationInvoker。而对于接口级注服务引用InterfaceCompatibleRegistryProtocol来说,将会返回MigrationInvoker。
  3. 调用interceptInvoker方法拦截invoker。实际上就是获取所有RegistryProtocolListener然后执行他们的onRefer,可用于在服务引用时更改invoker的行为和配置。
/**
 * RegistryProtocol的方法
 * 引用服务
 *
 * @param cluster    支持不同容错策略的Cluster,默认FailoverCluster
 * @param registry   注册中心操作类。例如ServiceDiscoveryRegistry、ZookeeperRegistry
 * @param type       服务接口
 * @param url        注册中心协议url
 * @param parameters 服务引用参数
 * @return Invoker
 */
protected <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url, Map<String, String> parameters) {
    //获取消费者属性,即attributes属性,内部包括:serviceModel、refer(服务引用参数)、scopeModel
    Map<String, Object> consumerAttribute = new HashMap<>(url.getAttributes());
    consumerAttribute.remove(REFER_KEY); //移除refer,因为方法参数已经传递了
    //获取protocol属性,只调用指定协议的服务提供方,其它协议忽略,默认值consumer
    String p = isEmpty(parameters.get(PROTOCOL_KEY)) ? CONSUMER : parameters.get(PROTOCOL_KEY);
    //构建服务配置url
    URL consumerUrl = new ServiceConfigURL(
        //协议,如不指定默认consumer
        p,
        //用户名
        null,
        //密码
        null,
        //ip
        parameters.get(REGISTER_IP_KEY),
        //端口0,path服务接口全路径
        0, getPath(parameters, type),
        parameters,
        consumerAttribute
    );
    //将当前consumerUrl存入注册中心协议url的属性缓存中,key为CONSUMER_URL
    url = url.putAttribute(CONSUMER_URL_KEY, consumerUrl);
    /*
     * 获取可迁移Invoker
     */
    ClusterInvoker<T> migrationInvoker = getMigrationInvoker(this, cluster, registry, type, url, consumerUrl);
    //拦截invoker
    return interceptInvoker(migrationInvoker, url, consumerUrl);
}

2.3.1 getMigrationInvoker获取可迁移Invoker

对于使用了应用级注册中心协议的RegistryProtocol来说,getMigrationInvoker方法将会返回一个ServiceDiscoveryMigrationInvoker。而对于接口级注册中心协议的InterfaceCompatibleRegistryProtocol来说,将会返回MigrationInvoker,他们都是可迁移Invoker。其中ServiceDiscoveryMigrationInvoker继承了MigrationInvoker。
image.png
RegistryProtocol:

/**
 * RegistryProtocol的方法
 * 获取可迁移Invoker
 *
 * @param registryProtocol 注册表协议
 * @param cluster          集群
 * @param registry         注册中心
 * @param type             接口类型
 * @param url              注册中心协议url
 * @param consumerUrl      消费者url
 * @return 可迁移Invoker
 */
protected <T> ClusterInvoker<T> getMigrationInvoker(RegistryProtocol registryProtocol, Cluster cluster, Registry registry, Class<T> type, URL url, URL consumerUrl) {
    return new ServiceDiscoveryMigrationInvoker<T>(registryProtocol, cluster, registry, type, url, consumerUrl);
}

InterfaceCompatibleRegistryProtocol:

/**
 * InterfaceCompatibleRegistryProtocol的方法
 * 获取可迁移Invoker
 *
 * @param registryProtocol 注册表协议
 * @param cluster          集群
 * @param registry         注册中心
 * @param type             接口类型
 * @param url              注册中心协议url
 * @param consumerUrl      消费者url
 * @return 可迁移Invoker
 */
@Override
protected <T> ClusterInvoker<T> getMigrationInvoker(RegistryProtocol registryProtocol, Cluster cluster, Registry registry, Class<T> type, URL url, URL consumerUrl) {
    //ClusterInvoker<T> invoker = getInvoker(cluster, registry, type, url);
    //获取MigrationInvoker
    return new MigrationInvoker<T>(registryProtocol, cluster, registry, type, url, consumerUrl);
}

所谓迁移,实际上是Dubbo 3的概念,因为Dubbo 3引入应用级服务发现,因此2.x 版本升级到3.x版本的时候,将会涉及到从接口级服务发现迁移至应用级服务发现。

Dubbo3的消费者和提供者一样,具备同时发现 2.x 与 3.x 提供者地址列表的能力,即服务双订阅,而这个可迁移Invoker,实际上表示消费者能够自动选择2.x 或者 3.x中的一种来消费。

在 Dubbo 3 之前地址注册模型是以接口级粒度注册到注册中心的,而 Dubbo 3 全新的应用级注册模型注册到注册中心的粒度是应用级的。从注册中心的实现上来说是几乎不一样的,这导致了对于从接口级注册模型获取到的 invokers 是无法与从应用级注册模型获取到的 invokers 进行合并的。为了帮助用户从接口级往应用级迁移,Dubbo 3 设计了 Migration 机制,基于三个状态的切换实现实际调用中地址模型的切换。这就是迁移的含义,而可迁移,就是可以在这三个状态之间动态转换。

三种迁移状态:FORCE_INTERFACE(强制接口级),APPLICATION_FIRST(应用级优先)、FORCE_APPLICATION(强制应用级)。

  1. FORCE_INTERFACE:只启用兼容模式下接口级服务发现的注册中心逻辑,调用流量 100% 走原有流程。
  2. APPLICATION_FIRST:开启接口级、应用级双订阅,运行时根据阈值和灰度流量比例动态决定调用流量走向。
  3. FORCE_APPLICATION:只启用新模式下应用级服务发现的注册中心逻辑,调用流量 100% 走应用级订阅的地址。

相关文档:

  1. https://dubbo.apache.org/zh/docs3-v2/java-sdk/upgrades-and-compatibility/service-discovery/
  2. https://dubbo.apache.org/zh/docs3-v2/java-sdk/upgrades-and-compatibility/service-discovery/migration-service-discovery/#3-consumer-端升级过程
  3. https://dubbo.apache.org/zh/docs3-v2/java-sdk/upgrades-and-compatibility/service-discovery/service-discovery-rule/

2.3.2 interceptInvoker拦截invoker

该方法尝试加载所有的RegistryProtocolListener定义,这些定义用于通过与已定义的交互来控制调用程序的行为,然后使用这些侦听器来更改MigrationInvoker的状态和行为,最后返回MigrationInvoker。

说白了,引入RegistryProtocol监听器是为了让用户有机会定制或更改RegistryProtocol的导出和引用行为。

该方法的大概逻辑为:

  1. 根据url参数registry.protocol.listener作为扩展名获取RegistryProtocolListener的Dubbo SPI实现,如果没有该参数则获取全部实现列表。
  2. 遍历所有RegistryProtocolListener,依次调用他们的onRefer方法,用于更改invoker。

当前版本可用的监听器仅一个MigrationRuleListener,它用于通过动态更改规则来控制迁移行为,同时它的onRefer方法才是真正的服务引入的方法,关于它的源码我们下文分析。

/**
 * RegistryProtocol的方法
 * <p>
 * 拦截Invoker
 * <p>
 * 该方法尝试加载所有的RegistryProtocolListener定义,这些定义用于通过与已定义的交互来控制调用程序的行为,然后使用这些侦听器来更改MigrationInvoker的状态和行为。
 * 当前版本可用的监听器是MigrationRuleListener,它用于通过动态更改规则来控制迁移行为。
 *
 * @param invoker     MigrationInvoker,它确定要使用哪种类型的调用程序列表
 * @param url         原始url,例如service-discovery-registry://47.94.229.245:2181/org.apache.dubbo.registry.RegistryService?REGISTRY_CLUSTER=demo1&application=demo-consumer&dubbo=2.0.2&pid=8228&registry=zookeeper&registry-type=service&registry.type=service&timeout=20000&timestamp=1666884631226
 * @param consumerUrl 表示当前接口及其配置的使用者url
 * @param <T>         服务定义
 * @return 传入的MigrationInvoker
 */
protected <T> Invoker<T> interceptInvoker(ClusterInvoker<T> invoker, URL url, URL consumerUrl) {
    //根据url参数registry.protocol.listener作为扩展名获取RegistryProtocolListener的Dubbo SPI实现,如果没有该参数则获取全部实现列表
    //当前版本可用的监听器仅一个MigrationRuleListener,它用于通过动态更改规则来控制迁移行为。
    List<RegistryProtocolListener> listeners = findRegistryProtocolListeners(url);
    //没有监听器直接返回
    if (CollectionUtils.isEmpty(listeners)) {
        return invoker;
    }
    //遍历所有监听器,执行监听器的onRefer方法
    for (RegistryProtocolListener listener : listeners) {
        listener.onRefer(this, invoker, consumerUrl, url);
    }
    return invoker;
}

3 InterfaceCompatibleRegistryProtocol接口级远程服务引入协议

接口级服务远程导出协议以registry开头,其对应的Protocol实现就是InterfaceCompatibleRegistryProtocol。

实际上InterfaceCompatibleRegistryProtocol继承了RegistryProtocol,大部分代码都是一样的,例如refer引入服务的方法,仅仅是一些细微的代码不一致。我们来看看它在refer过程中的特有的方法。
image.png

3.1 getRegistryUrl获取注册中心url

该方法根据远程服务协议url获取注册中心url,会对远程服务发现协议url进行处理、还原。获取url中的registry属性,也就是真实的注册中心协议,例如zookeeper,默认dubbo,然后替换掉registry协议并删除registry属性返回。

即InterfaceCompatibleRegistryProtocol#getRegistryUrl方法主要是将远程服务发现协议url更换为真实注册中心协议url。

/**
 * InterfaceCompatibleRegistryProtocol的方法
 * 
 * @param url 远程服务发现协议url
 * @return
 */
@Override
protected URL getRegistryUrl(URL url) {
    return URLBuilder.from(url)
        //获取url中的registry属性,也就是真实的注册中心协议,例如zookeeper,默认dubbo,然后替换掉registry协议
            .setProtocol(url.getParameter(REGISTRY_KEY, DEFAULT_REGISTRY))
        //删除registry属性
            .removeParameter(REGISTRY_KEY)
            .build();
}

4 getProxy创建服务接口代理对象

在进行了服务引入并创建了服务引入Invoker之后,在最后会调用proxyFactory#getProxy方法根据invoker创建一个服务接口代理对象返回。

这里的proxyFactory是ProxyFactory的自适应扩展实现,即ProxyFactory$Adaptive,也就是说会根据传入的url中的参数proxy的值选择对应的代理工厂实现类,而默认实现就是JavassistProxyFactory,因为它速度更快。最后通过真正的代理工厂实现类的getProxy方法创建服务代理对象。

在此前学习Dubbo服务发布的时候,同样是调用proxyFactory#getInvoker方法创建的一个代理Invoker实例对象的。

/**
 * ProxyFactory$Adaptive的方法
 * 创建服务代理对象
 * @param arg0 服务引入invoker
 * @param arg1 是否是泛型接口
 * @return 服务代理对象
 * @throws org.apache.dubbo.rpc.RpcException
 */
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");
    //注册中心协议url
    org.apache.dubbo.common.URL url = arg0.getUrl();
    //获取url中的proxy参数,默认javassist
    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])");
    ScopeModel scopeModel = ScopeModelUtil.getOrDefault(url.getScopeModel(), org.apache.dubbo.rpc.ProxyFactory.class);
    //基于Dubbo SPi机制查找指定名字的实现,默认JavassistProxyFactory
    org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory) scopeModel.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
    //通过ProxyFactory的getProxy方法获取服务代理对象
    return extension.getProxy(arg0, arg1);
}

4.1 JavassistRpcProxyFactory#getProxy

基于Javassist创建给定接口的代理对象,调用处理器为InvokerInvocationHandler,内部封装了invoker的调用。javassist创建失败之后,回退到基于jdk动态代理的方式创建代理对象。

/**
 * JavassistRpcProxyFactory的方法
 *
 * 将invoker包装成一个服务接口的代理对象实例
 *
 * @param invoker 服务引入invoker
 * @param interfaces 服务接口
 * @return 服务接口的代理对象实例
 * @param <T>
 */
@Override
@SuppressWarnings("unchecked")
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
    try {
        //基于Javassist创建给定接口的代理对象,调用处理器为InvokerInvocationHandler,内部封装了invoker的调用
        return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
    } catch (Throwable fromJavassist) {
        // try fall back to JDK proxy factory
        try {
            //javassist创建失败之后,回退到基于jdk动态代理的方式创建代理对象
            T proxy = jdkProxyFactory.getProxy(invoker, interfaces);
            logger.error("Failed to generate proxy by Javassist failed. Fallback to use JDK proxy success. " +
                "Interfaces: " + Arrays.toString(interfaces), fromJavassist);
            return proxy;
        } catch (Throwable fromJdk) {
            logger.error("Failed to generate proxy by Javassist failed. Fallback to use JDK proxy is also failed. " +
                "Interfaces: " + Arrays.toString(interfaces) + " Javassist Error.", fromJavassist);
            logger.error("Failed to generate proxy by Javassist failed. Fallback to use JDK proxy is also failed. " +
                "Interfaces: " + Arrays.toString(interfaces) + " JDK Error.", fromJdk);
            throw fromJavassist;
        }
    }
}

4.2 InvokerInvocationHandler调用处理器

InvokerInvocationHandler实现了InvocationHandler接口,所有对于代理对象的方法调用最终都会被转发到InvokerInvocationHandler的invoke方法上来,在该方法中能够获取到调用的接口方法和参数等信息,这样就是依靠内部的invoker对于真实的服务提供者实现发起本地或者远程调用了。

具体的发起调用的源码,我们在后面讲consumer服务调用的时候再详细分析。

public class InvokerInvocationHandler implements InvocationHandler {
    private static final Logger logger = LoggerFactory.getLogger(InvokerInvocationHandler.class);

    private final Invoker<?> invoker;

    private final ServiceModel serviceModel;

    private final String protocolServiceKey;

    public InvokerInvocationHandler(Invoker<?> handler) {
        this.invoker = handler;
        //consumerUrl
        URL url = invoker.getUrl();
        //协议服务key,格式为 {group}/{serviceName}:{version},例如greeting/org.apache.dubbo.demo.GreetingService:1.0.0
        this.protocolServiceKey = url.getProtocolServiceKey();
        this.serviceModel = url.getServiceModel();
    }

    /**
     * InvokerInvocationHandler的方法
     *
     * 执行代理对象的方法的转发
     *
     * @param proxy 在其上调用方法的代理实例
     *
     * @param method 在代理实例上调用的接口方法
     *
     * @param args 一个对象数组,包含在代理实例的方法调用中传递的参数值,如果接口方法不接受参数,则为null
     *
     * @return 调用代理对象方法执行结果
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //对于object类的方法,直接反射调用
        if (method.getDeclaringClass() == Object.class) {
            return method.invoke(invoker, args);
        }
        //获取方法名和方法参数类型数组
        String methodName = method.getName();
        Class<?>[] parameterTypes = method.getParameterTypes();
        //特殊方法的调用,直接调用invoker对象的同名方法
        if (parameterTypes.length == 0) {
            if ("toString".equals(methodName)) {
                return invoker.toString();
            } else if ("$destroy".equals(methodName)) {
                invoker.destroy();
                return null;
            } else if ("hashCode".equals(methodName)) {
                return invoker.hashCode();
            }
        } else if (parameterTypes.length == 1 && "equals".equals(methodName)) {
            return invoker.equals(args[0]);
        }
        //构建一个RpcInvocation作为接口方法调用抽象,包含方法名字,接口名,方法参数等信息
        RpcInvocation rpcInvocation = new RpcInvocation(serviceModel, method.getName(), invoker.getInterface().getName(), protocolServiceKey, method.getParameterTypes(), args);

        if (serviceModel instanceof ConsumerModel) {
            rpcInvocation.put(Constants.CONSUMER_MODEL, serviceModel);
            rpcInvocation.put(Constants.METHOD_MODEL, ((ConsumerModel) serviceModel).getMethodModel(method));
        }
        //通过InvocationUtil.invoke方法来发起真正的远程or本地调用
        return InvocationUtil.invoke(invoker, rpcInvocation);
    }
}

5 总结

本次我们学习了Dubbo3.1版本的服务引入的总体流程,现在简单总结下:

  1. 首先是Dubbo服务引入的入口,在Dubbo 3.1中,它位于监听器DubboDeployApplicationListener#onApplicationEvent方法中,在spring容器启动的最后一个步也就是refresh方法内部最后的finishRefresh方法中,将会向所有监听器发布一个ContextRefreshedEvent事件,表示容器刷新完毕,此时就会触发这个方法的调用。在Dubbo3.1中,默认启动项目就会进行服务引入,即饿汉式。
  2. onApplicationEvent方法内部通过DefaultModuleDeployer#start方法开启服务的导出、引用等操作,最终在startSync方法内部的referServices方法中,将会获取全部的dubbo服务引入bean实例ReferenceConfig,然后依次进行服务引入。
  3. 服务引入的全流程在ReferenceConfig#createProxy方法中,该方法首先尝试创建invoker,也就是进行服务引入的逻辑。
    1. 服务引入分为本地引入和远程引入,本地引入的invoker内部不需要创建netty客户端,因为不会发送网络请求,而是直接调用本服务的接口,远程引入则会创建netty客户端,用以发送rpc请求
    2. 对于远程引入,获取的是MigrationInvoker可迁移Invoker,用于dubbo2.x和dubbo3.x之间的接口级别和应用级别的服务发现迁移。
    3. 创建MigrationInvoker并没有真正涉及到服务的引入**,在interceptInvoker方法中,该方法将会对invoker应用MigrationRuleListener#onRefer方法,该方法才是真正的服务引入入口,MigrationRuleListener以及真正的服务引入的逻辑,我们后面学习。**
  4. 然后调用sproxyFactory#getProxy方法根据invoker创建一个服务接口代理对象返回,默认基于Javassist的代理。这样就完成了服务的引入。

后面我们将会继续学习MigrationRuleListener#onRefer方法,该方法才是真正的服务引入入口,MigrationRuleListener以及真正的服务引入的逻辑,以及服务迁移到底是个什么东西?我们后面学习。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/693417.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

整除及求余运算符、数字的提取、顺序结构程序

1.运算符 在有余数的除法运算中&#xff0c;如果要知道商和余数分别是多少&#xff0c;可以用/和%这两个运算符号来得到。 (1)/(整除)&#xff0c;当被除数和除数均为整数时&#xff0c;结果也为整型&#xff0c;只取商的整数部分。 如:10/25 10/33 5/10 0 (2)%(求余)&…

Knife4j 生成 API 文档

文章目录 Knife4j 简介使用步骤Knife4j 常用注解的列表案例可能遇到报错 Knife4j 简介 Knife4j 是一个增强的 Swagger 文档生成工具&#xff0c;提供了更加友好的界面和更多功能&#xff0c;使得 API 文档更加美观且易于使用。它是基于 Spring Boot 和 Swagger 进行封装的&…

5分钟快速带了解fl studio21破解汉化版安装激活指南

随着数字音乐制作的快速发展&#xff0c;越来越多的音乐制作软件涌现出来&#xff0c;而FL Studio无疑是其中的佼佼者。作为一款功能强大、易于上手的音乐制作软件&#xff0c;FL Studio V21中文版在继承了前代版本优秀基因的基础上&#xff0c;进一步提升了用户体验&#xff0…

Databricks Data Warehouse

Warehouse features 原来的data warehouse痛点&#xff1a; 用例不兼容的支持模型的安全和管理不兼容不相交和重复的数据 ETL workloads Streaming Architecture Data Science and ML

《软件定义安全》之三:用软件定义的理念做安全

第3章 用软件定义的理念做安全 1.不进则退&#xff0c;传统安全回到“石器时代” 1.1 企业业务和IT基础设施的变化 随着企业办公环境变得便利&#xff0c;以及对降低成本的天然需求&#xff0c;企业始终追求IT集成设施的性价比、灵活性、稳定性和开放性。而云计算、移动办公…

06 Linux 设备驱动模型

1、Overview Linux-2.6 引入的新的设备管理机制 - kobject 降低设备多样性带来的 Linux 驱动开发的复杂度,以及设备热拔插处理、电源管理等将硬件设备归纳、分类,然后抽象出一套标准的数据结构和接口驱动的开发,就简化为对内核所规定的数据结构的填充和实现驱动模型是 Linu…

XUbuntu24.04之ch9344(usb转串口芯片)安装驱动(二百四十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

【C#】开发过程中记录问题

1.DateTimePicker控件获取时间 拖动控件,设置属性format为custom格式。例如我想获得20240101这种类型的string类型的数据: string DateTime = DateTimePicker.Value.ToString("yyyyMMdd");2.ComboBox下拉列表控件 默认为DropDown,下拉可修改。 DropDownList为下…

【计算机网络】P3 计算机网络协议、接口、服务的概念、区别以及计算机网络提供的三种服务方式

目录 协议什么是协议协议是水平存活的协议的组成 接口服务服务是什么服务原语 协议与服务的区别计算机网络提供的服务的三种方式面向连接服务与无连接服务可靠服务与不可靠服务有应答服务与无应答服务 协议 什么是协议 协议&#xff0c;就是规则的集合。 在计算机网络中&…

workerman error 2 send buffer full and drop package

来源 报错信息&#xff1a;workerman error 2 send buffer full and drop package 定时发送数据的时候&#xff0c;本地偶尔出现这种情况 线上第一条数据发出去就报错了&#xff0c;数据改小一点可以发&#xff0c;不过一会还是会出现这种情况。 解决 根据我的经验&#xf…

星舰四飞成功!SpaceX 今年还要飞 4 次?星舰未来 10 年规划展望

SpaceX 的星舰&#xff08;Starship&#xff09;项目一直备受瞩目&#xff0c;最近的第四次试飞再次引发了全球关注。本文将详细回顾星舰第四次发射的成功经验&#xff0c;并探讨其未来的十年规划。 一、引言 星舰是 SpaceX 研制的下一代重型运载火箭系统&#xff0c;旨在实现…

[经验] 场效应管是如何发挥作用的 #知识分享#学习方法#职场发展

场效应管是如何发挥作用的 在现代电子技术领域&#xff0c;场效应管&#xff08;MOSFET&#xff09;是一种重要的半导体元器件。它的作用非常广泛&#xff0c;例如在集成电路中扮演着关键的角色。在本文中&#xff0c;我们将详细探讨场效应管的作用及其在实际应用中的意义。 简…

关于烫烫烫和屯屯屯

微较的msvc编译器&#xff0c;调试模式下为了方便检测内存的非法访问&#xff0c;对于不同的内存做了初始化&#xff0c; 未初始化栈&#xff1a; 0xCCCCCCCC 未初始化堆&#xff1a; 0xCDCDCDCD 已释放的堆&#xff1a; 0xDDDDDDDD 0xCCCC解释为GB2312字符即是烫&#xff…

vue27:脚手架详细介绍main.js

在 Vue.js 中&#xff0c;render 函数是一个可选的选项&#xff0c;它允许你自定义组件的渲染逻辑。 如果你没有在 Vue 实例中提供 render 函数&#xff0c;Vue 将使用模板&#xff08;template&#xff09;来生成虚拟 DOM。 以下是render / template 两种方式的比较&#…

Recognize Anything: A Strong Image Tagging Model(RAM模型使用方法)

一、RAM模型介绍 这篇论文介绍了一个名为“Recognize Anything Model”&#xff08;RAM&#xff09;的新型基础模型&#xff0c;专用于图像标签识别&#xff08;图像分类&#xff09;。这一模型采用大规模图像-文本配对数据进行训练&#xff0c;无需手动注释&#xff0c;能够在…

OpenCV-绘制虚线

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 功能函数 // 绘制虚线 void DrawDottedLine(cv::Mat &input, cv::Point p1, cv::Point p2, cv::Scalar color, int thickne…

前端计网面试题(二)

一、在浏览器中输入url并且按下回车之后发生了什么&#xff1f; 首先解析url&#xff0c;判断url是否合法&#xff0c;如果合法再判断是否完整。如果不合法&#xff0c;则使用用户默认的搜索引擎进行搜索。DNS域名解析获取URL对应的ip地址。&#xff08;首先看本地是否有缓存&…

为什么会有虚像(完美解释焦距和像大小和透镜的关系)

本来我就打算写虚像相关的内容&#xff0c;实际上我看不懂光学的内容&#xff0c;我只是发觉书上没有使用变分法来做&#xff0c;而只是解析几何的变换&#xff0c;这个做法完全脱离实际&#xff0c;物理书为什么会这样写不知道原因&#xff0c;但是很明显这样的内容也非常的复…

如何学习自动化测试?(附教程)

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 自动化测试介绍 自动化测试(Automated Testing)&#xff0c;是指把以人为驱动的测试行为转化为…

SwiftUI五视图动画和转场

代码下载 使用SwiftUI可以把视图状态的改变转成动画过程&#xff0c;SwiftUI会处理所有复杂的动画细节。在这篇中&#xff0c;会给跟踪用户徒步的图表视图添加动画&#xff0c;使用animation(_:)修改器给一个视图添加动画效果非常容易。 下载起步项目并跟着本篇教程一步步实践…