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实例,同时内部保存着本地导出的服务缓存exporterMapreturn 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);//如果服务类型为RegistryServiceif (RegistryService.class.equals(type)) {//基于registry实例获取Invokerreturn proxyFactory.getInvoker((T) registry, type, url);}// group="a,b" or group="*"//获取服务引用属性mapMap<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) {//允许wrapperreturn getCluster(scopeModel, name, true);
}static Cluster getCluster(ScopeModel scopeModel, String name, boolean wrap) {//如果name为空,那么默认failoverif (StringUtils.isEmpty(name)) {name = Cluster.DEFAULT;}//基于Dubbo SPI获取,可能会经过wrapperreturn 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(服务引用参数)、scopeModelMap<String, Object> consumerAttribute = new HashMap<>(url.getAttributes());consumerAttribute.remove(REFER_KEY); //移除refer,因为方法参数已经传递了//获取protocol属性,只调用指定协议的服务提供方,其它协议忽略,默认值consumerString p = isEmpty(parameters.get(PROTOCOL_KEY)) ? CONSUMER : parameters.get(PROTOCOL_KEY);//构建服务配置urlURL consumerUrl = new ServiceConfigURL(//协议,如不指定默认consumerp,//用户名null,//密码null,//ipparameters.get(REGISTER_IP_KEY),//端口0,path服务接口全路径0, getPath(parameters, type),parameters,consumerAttribute);//将当前consumerUrl存入注册中心协议url的属性缓存中,key为CONSUMER_URLurl = url.putAttribute(CONSUMER_URL_KEY, consumerUrl);/** 获取可迁移Invoker*/ClusterInvoker<T> migrationInvoker = getMigrationInvoker(this, cluster, registry, type, url, consumerUrl);//拦截invokerreturn 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);//获取MigrationInvokerreturn 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");//注册中心协议urlorg.apache.dubbo.common.URL url = arg0.getUrl();//获取url中的proxy参数,默认javassistString 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机制查找指定名字的实现,默认JavassistProxyFactoryorg.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 factorytry {//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;//consumerUrlURL url = invoker.getUrl();//协议服务key,格式为 {group}/{serviceName}:{version},例如greeting/org.apache.dubbo.demo.GreetingService:1.0.0this.protocolServiceKey = url.getProtocolServiceKey();this.serviceModel = url.getServiceModel();}/*** InvokerInvocationHandler的方法** 执行代理对象的方法的转发** @param proxy 在其上调用方法的代理实例** @param method 在代理实例上调用的接口方法** @param args 一个对象数组,包含在代理实例的方法调用中传递的参数值,如果接口方法不接受参数,则为null** @return 调用代理对象方法执行结果*/@Overridepublic 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以及真正的服务引入的逻辑,以及服务迁移到底是个什么东西?我们后面学习。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

分享不用会员免费听歌的软件,可听付费,支持随听随下!

今天来点特别的&#xff0c;给你们带来几款全网免费听歌的神器&#xff0c;让你们的音乐之旅不再有障碍&#xff01; 现在&#xff0c;找好听的歌越来越像寻宝一样&#xff0c;动不动就得掏腰包。不过别担心&#xff0c;阿星今天就来分享几款好用的免费听歌app&#xff0c;电脑…

六、【源码】SQL执行器的定义和实现

源码地址&#xff1a;https://github.com/mybatis/mybatis-3/ 仓库地址&#xff1a;https://gitcode.net/qq_42665745/mybatis/-/tree/06-sql-executor SQL执行器的定义和实现 之前的Sql执行都是耦合在SqlSession里的&#xff0c;现在要对这部分进行解耦和重构&#xff0c;引…

【MySQL】(基础篇四) —— 检索数据

检索数据 检索数据是我们使用数据库时进行最多的操作&#xff0c;其中包括了检索条件、排序、过滤、分组等等。我会在后续的多篇博客中为你进行详细地介绍它们。 这次先让我们来粗略的了解一下SELECT&#xff0c;为了使用SELECT检索表数据&#xff0c;必须至少明确两点信息—…

【JsDoc】JsDoc用法 | 巧妙用法

type type {other} other 接收表达式或字符 1、数组代码提示 1、效果图 1、码 /*** type {Array.<play|paush|next>} */ let music []2、字符串提示 2、效果图 2、码 /*** type {a|b|c}*/ let str

项目-五子棋双人对战:游戏房间的管理(5)

完整代码见: 邹锦辉个人所有代码: 测试仓库 - Gitee.com 之前我们已经实现了玩家匹配的功能, 我们都知道, 匹配完过后就可以进入游戏房间进行对战了, 所以我们下一步关注的重点就是对于游戏房间的管理. 模块详细讲解 功能需求 通过匹配的方式, 自动给玩家加入到一个游戏房间…

atomic特质的局限性

为什么在实际的 Objective-C 开发中, 几乎所有的属性都声明为 nonatomic ? 声明为 atomic 的属性我是真的没见过 在实际的 Objective-C 开发中&#xff0c;大多数属性通常声明为 nonatomic&#xff0c;主要原因包括性能考虑和常见的设计模式。具体原因如下&#xff1a; 性能问…

20240606更新Toybrick的TB-RK3588开发板在Android12下的内核

20240606更新Toybrick的TB-RK3588开发板在Android12下的内核 2024/6/6 10:51 0、整体编译&#xff1a; 1、cat android12-rk-outside.tar.gz* | tar -xzv 2、cd android12 3、. build/envsetup.sh 4、lunch rk3588_s-userdebug 5、./build.sh -AUCKu -d rk3588-toybrick-x0-a…

Clo3D导出服装动画,使用Unity3D展示

1.前言 Clo3D是一款应用于时装行业的3D服装设计软件,其强大的布料模拟算法可在3D空间中实现设计、制版、试衣和走秀,大幅提升数字作品逼真度和制作效率。为了让服装动画效果展示在Unity3D上模拟效果&#xff0c;需要Clo3D模拟出逼着的衣服动画。总体流程为Clo3D - Mixamo -Blen…

登Cell Press子刊,武汉理工大学团队基于集成学习提出简化电化学模型,0.17s完成3500s的1C恒流放电

2022 年 7 月&#xff0c;不老男神林志颖突发车祸&#xff0c;作为专业赛车手的他驾驶的特斯拉 Model X 在行驶过程中忽然偏离既定轨迹&#xff0c;一头撞向路边的隔离带&#xff0c;随后车辆起火&#xff0c;并在救援车拖吊过程中二次起火&#xff0c;最终整辆车被烧到只剩下了…

Java心跳检测机制

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 心跳检测的定义 心跳检测是一种监控机制&#xff0c;在Java编程和分布式系统中具有广泛的应用。心跳检测&#xff0c;顾名思义&#xff0c;就像心跳一样&#xff0c;是一种…

【JS】理解闭包及其应用

历史小剧场 明朝灭亡&#xff0c;并非是简单的政治问题&#xff0c;事实上&#xff0c;这是世界经济史上的一个重要案例。 所谓没钱&#xff0c;就是没有白银。----《明朝那些事儿》 什么是闭包&#xff1f; 闭包就是指有权访问另一个函数作用域中变量的函数 闭包变量存储位置&…

Lidar3607.2 雷达点云数据处理软件新增功能介绍

新特性:预处理航带平差新增livox激光器镜面误差改正,新增多源航带平差&#xff0c;提升点云和影像匹配精度优化配准功能流程&#xff0c;ICP功能支持点云与模型配准安置检校新增轨迹自动裁剪轨迹解算时投影坐标增加Z值记录数据管理新增点云色彩亮度和对比度调节新增多段线平滑工…

Python中报错提示:TypeError: Student() takes no arguments

Python中报错提示&#xff1a;TypeError: Student() takes no arguments 在Python编程中&#xff0c;类是创建对象的蓝图。每个类都可能包含一个特殊的方法__init__&#xff0c;我们称之为构造函数&#xff0c;它在创建新实例时被调用。如果你在尝试创建一个类的实例时遇到了Ty…

【SpringCloud学习笔记】Docker(中篇)

Docker 1. 自定义镜像 前面我们都是使用docker pull拉取仓库中现成的镜像&#xff0c;但是如果我们想要将一个Java应用程序构建成镜像然后部署应该怎么做呢&#xff1f;这个时候我们就需要自定义镜像了 **镜像&#xff1a;**本质上就是一堆文件的集合&#xff0c;包含了应用程…

【清华大学】《自然语言处理》(刘知远)课程笔记 ——NLP Basics

自然语言处理基础&#xff08;Natural Language Processing Basics, NLP Basics&#xff09; 自然语言处理( Natural Language Processing, NLP)是计算机科学领域与人工智能领域中的一个重要方向。它研究能实现人与计算机之间用自然语言进行有效通信的各种理论和方法。自然语言…

临床应用的深度学习在视网膜疾病的诊断和转诊中的应用| 文献速递-视觉通用模型与疾病诊断

Title 题目 Clinically applicable deep learning for diagnosis and referral in retinal disease 临床应用的深度学习在视网膜疾病的诊断和转诊中的应用 01 文献速递介绍 诊断成像的数量和复杂性正在以比人类专家可用性更快的速度增加。人工智能在分类一些常见疾病的二…

【Node.js快速部署opencv项目】图像分类与目标检测

⭐️我叫忆_恒心&#xff0c;一名喜欢书写博客的研究生&#x1f468;‍&#x1f393;。 如果觉得本文能帮到您&#xff0c;麻烦点个赞&#x1f44d;呗&#xff01; 近期会不断在专栏里进行更新讲解博客~~~ 有什么问题的小伙伴 欢迎留言提问欧&#xff0c;喜欢的小伙伴给个三连支…

高考之后第一张大流量卡应该怎么选?

高考之后第一张大流量卡应该怎么选&#xff1f; 高考结束后&#xff0c;选择一张合适的大流量卡对于准大学生来说非常重要&#xff0c;因为假期期间流量的使用可能会暴增。需要综合考虑多个因素&#xff0c;以确保选到最适合自己需求、性价比较高且稳定的套餐。以下是一些建议…

Linux——PXE整体流程

1.自己安装一个CentOS 8的服务器 1&#xff09;手动安装 虚拟硬件配置&#xff1a;2核CPU&#xff0c;4G内存&#xff0c;100G硬盘 2个网卡&#xff08;一个通外网&#xff0c;一个内部使用&#xff09; 软件安装&#xff1a;Server GUI 磁盘分区&#xff1a;使用逻辑卷&#…

Django API开发实战:前后端分离、Restful风格与DRF序列化器详解

系列文章目录 Django入门全攻略&#xff1a;从零搭建你的第一个Web项目Django ORM入门指南&#xff1a;从概念到实践&#xff0c;掌握模型创建、迁移与视图操作Django ORM实战&#xff1a;模型字段与元选项配置&#xff0c;以及链式过滤与QF查询详解Django ORM深度游&#xff…