1.服务引用原理

Dubbo 服务引用的时机有两个,第一个是在 Spring 容器调用 ReferenceBean 的 afterPropertiesSet 方法时引用服务,第二个是在 ReferenceBean 对应的服务被注入到其他类中时引用。这两个引用服务的时机区别在于,第一个是饿汉式的,第二个是懒汉式的。默认情况下,Dubbo 使用懒汉式引用服务。如果需要使用饿汉式,可通过配置 dubbo:reference 的 init 属性开启。下面我们按照 Dubbo 默认配置进行分析,整个分析过程从 ReferenceBean 的 getObject 方法开始。当我们的服务被注入到其他类中时,Spring 会第一时间调用 getObject 方法,并由该方法执行服务引用逻辑。按照惯例,在进行具体工作之前,需先进行配置检查与收集工作。接着根据收集到的信息决定服务用的方式,有三种,第一种是引用本地 (JVM) 服务,第二是通过直连方式引用远程服务,第三是通过注册中心引用远程服务。不管是哪种引用方式,最后都会得到一个 Invoker 实例。如果有多个注册中心,多个服务提供者,这个时候会得到一组 Invoker 实例,此时需要通过集群管理类 Cluster 将多个 Invoker 合并成一个实例。合并后的 Invoker 实例已经具备调用本地或远程服务的能力了,但并不能将此实例暴露给用户使用,这会对用户业务代码造成侵入。此时框架还需要通过代理工厂类 (ProxyFactory) 为服务接口生成代理类,并让代理类去调用 Invoker 逻辑。避免了 Dubbo 框架代码对业务代码的侵入,同时也让框架更容易使用。

2.源码分析

服务引用的入口方法为 ReferenceBean 的 getObject 方法,该方法定义在 Spring 的 FactoryBean 接口中,ReferenceBean 实现了这个方法。实现代码如下:

  1. public Object getObject() throws Exception {
  2. return get();
  3. }
  4. public synchronized T get() {
  5. if (destroyed) {
  6. throw new IllegalStateException("Already destroyed!");
  7. }
  8. // 检测 ref 是否为空,为空则通过 init 方法创建
  9. if (ref == null) {
  10. // init 方法主要用于处理配置,以及调用 createProxy 生成代理类
  11. init();
  12. }
  13. return ref;
  14. }

2.1处理配置

Dubbo 提供了丰富的配置,用于调整和优化框架行为,性能等。Dubbo 在引用或导出服务时,首先会对这些配置进行检查和处理,以保证配置的正确性。配置解析逻辑封装在 ReferenceConfig 的 init 方法中,下面进行分析。

  1. public synchronized void init() {
  2. // 避免重复初始化
  3. if (initialized) {
  4. return;
  5. }
  6. if (bootstrap == null) {
  7. bootstrap = DubboBootstrap.getInstance();
  8. bootstrap.init();
  9. }
  10. //校验并初始化所有的本类中所有的Config配置类,不允许为空的则抛异常,允许为空的则尝试从全局配置中赋值
  11. checkAndUpdateSubConfigs();
  12. //检查本地存根合法性,Local将被弃用,替换成Stud,即:类全限定类名+Stud
  13. checkStubAndLocal(interfaceClass);
  14. ConfigValidationUtils.checkMock(interfaceClass, this);
  15. //配置一些说明参数
  16. Map<String, String> map = new HashMap<String, String>();
  17. map.put(SIDE_KEY, CONSUMER_SIDE);
  18. ReferenceConfigBase.appendRuntimeParameters(map);
  19. //检查是否为泛化类型
  20. if (!ProtocolUtils.isGeneric(generic)) {
  21. String revision = Version.getVersion(interfaceClass, version);
  22. if (revision != null && revision.length() > 0) {
  23. map.put(REVISION_KEY, revision);
  24. }
  25. //创建该类的的代理(此代理类放置在wapper类的WRAPPER_MAP缓存中)并获取方法
  26. String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
  27. if (methods.length == 0) {
  28. logger.warn("No method found in service interface " + interfaceClass.getName());
  29. map.put(METHODS_KEY, ANY_VALUE);
  30. } else {
  31. map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), COMMA_SEPARATOR));
  32. }
  33. }
  34. map.put(INTERFACE_KEY, interfaceName);
  35. AbstractConfig.appendParameters(map, getMetrics());
  36. AbstractConfig.appendParameters(map, getApplication());
  37. AbstractConfig.appendParameters(map, getModule());
  38. // remove 'default.' prefix for configs from ConsumerConfig
  39. // appendParameters(map, consumer, Constants.DEFAULT_KEY);
  40. AbstractConfig.appendParameters(map, consumer);
  41. AbstractConfig.appendParameters(map, this);
  42. MetadataReportConfig metadataReportConfig = getMetadataReportConfig();
  43. if (metadataReportConfig != null && metadataReportConfig.isValid()) {
  44. map.putIfAbsent(METADATA_KEY, REMOTE_METADATA_STORAGE_TYPE);
  45. }
  46. //有关asyn方法
  47. Map<String, AsyncMethodInfo> attributes = null;
  48. if (CollectionUtils.isNotEmpty(getMethods())) {
  49. attributes = new HashMap<>();
  50. for (MethodConfig methodConfig : getMethods()) {
  51. AbstractConfig.appendParameters(map, methodConfig, methodConfig.getName());
  52. String retryKey = methodConfig.getName() + ".retry";
  53. if (map.containsKey(retryKey)) {
  54. String retryValue = map.remove(retryKey);
  55. if ("false".equals(retryValue)) {
  56. map.put(methodConfig.getName() + ".retries", "0");
  57. }
  58. }
  59. AsyncMethodInfo asyncMethodInfo = AbstractConfig.convertMethodConfig2AsyncInfo(methodConfig);
  60. if (asyncMethodInfo != null) {
  61. attributes.put(methodConfig.getName(), asyncMethodInfo);
  62. }
  63. }
  64. }
  65. //获取本机ip
  66. String hostToRegistry = ConfigUtils.getSystemProperty(DUBBO_IP_TO_REGISTRY);
  67. if (StringUtils.isEmpty(hostToRegistry)) {
  68. hostToRegistry = NetUtils.getLocalHost();
  69. } else if (isInvalidLocalHost(hostToRegistry)) {
  70. throw new IllegalArgumentException("Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
  71. }
  72. map.put(REGISTER_IP_KEY, hostToRegistry);
  73. serviceMetadata.getAttachments().putAll(map);
  74. //创建代理类
  75. ref = createProxy(map);
  76. serviceMetadata.setTarget(ref);
  77. serviceMetadata.addAttribute(PROXY_CLASS_REF, ref);
  78. ConsumerModel consumerModel = repository.lookupReferredService(serviceMetadata.getServiceKey());
  79. consumerModel.setProxyObject(ref);
  80. consumerModel.init(attributes);
  81. initialized = true;
  82. // dispatch a ReferenceConfigInitializedEvent since 2.7.4
  83. //发布invoker创建成功的事件
  84. dispatch(new ReferenceConfigInitializedEvent(this, invoker));
  85. }

上面的代码很长,做的事情比较多。

1.检测各个Config的配置是否正常,再检测 ConsumerConfig 实例是否存在,如不存在则创建一个新的实例,然后通过系统变量或 dubbo.properties 配置文件填充 ConsumerConfig 的字段。接着是检测泛化配置,并根据配置设置 interfaceClass 的值。

2.将各个配置类上标注Parameter注解的属性按要求放入map中,并将map设置进serviceMetadata中

3.创建代理对象

4.发布事件

2.3. 引用服务

本节我们要从 createProxy 开始看起。从字面意思上来看,createProxy 似乎只是用于创建代理对象的。但实际上并非如此,该方法还会调用其他方法构建以及合并 Invoker 实例。具体细节如下。

  1. private T createProxy(Map<String, String> map) {
  2. //检测是否从本地调用服务方
  3. if (shouldJvmRefer(map)) {
  4. URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
  5. invoker = REF_PROTOCOL.refer(interfaceClass, url);
  6. if (logger.isInfoEnabled()) {
  7. logger.info("Using injvm service " + interfaceClass.getName());
  8. }
  9. } else {
  10. urls.clear();
  11. //用户指定了url,则采用直连的方式
  12. if (url != null && url.length() > 0) {
  13. String[] us = SEMICOLON_SPLIT_PATTERN.split(url);
  14. if (us != null && us.length > 0) {
  15. for (String u : us) {
  16. URL url = URL.valueOf(u);
  17. if (StringUtils.isEmpty(url.getPath())) {
  18. url = url.setPath(interfaceName);
  19. }
  20. if (UrlUtils.isRegistry(url)) {
  21. urls.add(url.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
  22. } else {
  23. urls.add(ClusterUtils.mergeUrl(url, map));
  24. }
  25. }
  26. }
  27. } else {
  28. //从注册中心发现服务
  29. if (!LOCAL_PROTOCOL.equalsIgnoreCase(getProtocol())) {
  30. //检测注册中心有效性
  31. checkRegistry();
  32. //获取url
  33. List<URL> us = ConfigValidationUtils.loadRegistries(this, false);
  34. if (CollectionUtils.isNotEmpty(us)) {
  35. for (URL u : us) {
  36. //尝试获取监控中心url
  37. URL monitorUrl = ConfigValidationUtils.loadMonitor(this, u);
  38. if (monitorUrl != null) {
  39. map.put(MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
  40. }
  41. urls.add(u.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
  42. }
  43. }
  44. if (urls.isEmpty()) {
  45. throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
  46. }
  47. }
  48. }
  49. // 单个注册中心或服务提供者(服务直连,下同)
  50. if (urls.size() == 1) {
  51. //创建invoker对象
  52. invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
  53. } else {
  54. // 多个注册中心或多个服务提供者,或者两者混合
  55. List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
  56. URL registryURL = null;
  57. for (URL url : urls) {
  58. invokers.add(REF_PROTOCOL.refer(interfaceClass, url));
  59. if (UrlUtils.isRegistry(url)) {
  60. registryURL = url; // use last registry url
  61. }
  62. }
  63. if (registryURL != null) {
  64. URL u = registryURL.addParameterIfAbsent(CLUSTER_KEY, ZoneAwareCluster.NAME);
  65. invoker = CLUSTER.join(new StaticDirectory(u, invokers));
  66. } else { // not a registry url, must be direct invoke.
  67. invoker = CLUSTER.join(new StaticDirectory(invokers));
  68. }
  69. }
  70. }
  71. //校验invoker有效性,即是否有提供者
  72. if (shouldCheck() && !invoker.isAvailable()) {
  73. throw new IllegalStateException("Failed to check the status of the service "
  74. + interfaceName
  75. + ". No provider available for the service "
  76. + (group == null ? "" : group + "/")
  77. + interfaceName +
  78. (version == null ? "" : ":" + version)
  79. + " from the url "
  80. + invoker.getUrl()
  81. + " to the consumer "
  82. + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
  83. }
  84. if (logger.isInfoEnabled()) {
  85. logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
  86. }
  87. /**
  88. * @since 2.7.0
  89. * ServiceData Store
  90. */
  91. String metadata = map.get(METADATA_KEY);
  92. WritableMetadataService metadataService = WritableMetadataService.getExtension(metadata == null ? DEFAULT_METADATA_STORAGE_TYPE : metadata);
  93. if (metadataService != null) {
  94. URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map);
  95. metadataService.publishServiceDefinition(consumerURL);
  96. }
  97. // 生成代理类
  98. return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));
  99. }

上面代码很多,不过逻辑比较清晰。首先根据配置检查是否为本地调用,若是,则调用 InjvmProtocol 的 refer 方法生成 InjvmInvoker 实例。若不是,则读取直连配置项,或注册中心 url,并将读取到的 url 存储到 urls 中。然后根据 urls 元素数量进行后续操作。若 urls 元素数量为1,则直接通过 Protocol 自适应拓展类构建 Invoker 实例接口。若 urls 元素数量大于1,即存在多个注册中心或服务直连 url,此时先根据 url 构建 Invoker。然后再通过 Cluster 合并多个 Invoker,最后调用 ProxyFactory 生成代理类。Invoker 的构建过程以及代理类的过程比较重要,因此接下来将分两小节对这两个过程进行分析。

2.3.1 创建 Invoker

Invoker 是 Dubbo 的核心模型,代表一个可执行体。在服务提供方,Invoker 用于调用服务提供类。在服务消费方,Invoker 用于执行远程调用。Invoker 是由 Protocol 实现类构建而来。Protocol 实现类有很多,本节会分析最常用的两个,分别是 RegistryProtocol 和 DubboProtocol,其他的大家自行分析。下面先来分析 RegisterProtocol的 refer 方法源码。如下:

REF_PROTOCOL通过SPI构建Filter和Listener链后,再走进RegisterProtocol的refer方法中

  1. public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
  2. url = getRegistryUrl(url);
  3. Registry registry = registryFactory.getRegistry(url);
  4. if (RegistryService.class.equals(type)) {
  5. return proxyFactory.getInvoker((T) registry, type, url);
  6. }
  7. // group="a,b" or group="*"
  8. Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
  9. String group = qs.get(GROUP_KEY);
  10. if (group != null && group.length() > 0) {
  11. if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
  12. return doRefer(getMergeableCluster(), registry, type, url);
  13. }
  14. }
  15. return doRefer(cluster, registry, type, url);
  16. }
  1. private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
  2. // 创建 RegistryDirectory 实例
  3. RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
  4. // 设置注册中心和协议
  5. directory.setRegistry(registry);
  6. directory.setProtocol(protocol);
  7. Map<String, String> parameters = new HashMap<String, String>(directory.getConsumerUrl().getParameters());
  8. // 生成服务消费者链接
  9. URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
  10. // 注册服务消费者,在 consumers 目录下创建新节点
  11. if (directory.isShouldRegister()) {
  12. directory.setRegisteredConsumerUrl(subscribeUrl);
  13. //注册消费者
  14. registry.register(directory.getRegisteredConsumerUrl());
  15. }
  16. directory.buildRouterChain(subscribeUrl);
  17. // 订阅 providers、configurators、routers 等节点数据
  18. directory.subscribe(toSubscribeUrl(subscribeUrl));
  19. // 一个注册中心可能有多个服务提供者,因此这里需要将多个服务提供者合并为一个
  20. Invoker<T> invoker = cluster.join(directory);
  21. //通过SPI机制获取org.apache.dubbo.registry.integration.RegistryProtocolListener的实现类
  22. List<RegistryProtocolListener> listeners = findRegistryProtocolListeners(url);
  23. if (CollectionUtils.isEmpty(listeners)) {
  24. return invoker;
  25. }
  26. RegistryInvokerWrapper<T> registryInvokerWrapper = new RegistryInvokerWrapper<>(directory, cluster, invoker, subscribeUrl);
  27. //将监听器构建成链并返回
  28. for (RegistryProtocolListener listener : listeners) {
  29. listener.onRefer(this, registryInvokerWrapper);
  30. }
  31. return registryInvokerWrapper;
  32. }

然后是DubboProtocol

  1. @Override
  2. public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException {
  3. optimizeSerialization(url);
  4. // 创建 DubboInvoker
  5. DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
  6. invokers.add(invoker);
  7. return invoker;
  8. }

上面方法看起来比较简单,不过这里有一个调用需要我们注意一下,即 getClients。这个方法用于获取客户端实例,实例类型为 ExchangeClient。ExchangeClient 实际上并不具备通信能力,它需要基于更底层的客户端实例进行通信。比如 NettyClient、MinaClient 等,默认情况下,Dubbo 使用 NettyClient 进行通信。接下来,我们简单看一下 getClients 方法的逻辑。

  1. private ExchangeClient[] getClients(URL url) {
  2. // 是否共享连接
  3. boolean service_share_connect = false;
  4. // 获取连接数,默认为0,表示未配置
  5. int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
  6. // 如果未配置 connections,则共享连接
  7. if (connections == 0) {
  8. service_share_connect = true;
  9. connections = 1;
  10. }
  11. ExchangeClient[] clients = new ExchangeClient[connections];
  12. for (int i = 0; i < clients.length; i++) {
  13. if (service_share_connect) {
  14. // 获取共享客户端
  15. clients[i] = getSharedClient(url);
  16. } else {
  17. // 初始化新的客户端
  18. clients[i] = initClient(url);
  19. }
  20. }
  21. return clients;
  22. }

这里根据 connections 数量决定是获取共享客户端还是创建新的客户端实例,默认情况下,使用共享客户端实例。getSharedClient 方法中也会调用 initClient 方法,因此下面我们一起看一下这两个方法。

  1. private ExchangeClient getSharedClient(URL url) {
  2. String key = url.getAddress();
  3. // 获取带有“引用计数”功能的 ExchangeClient
  4. ReferenceCountExchangeClient client = referenceClientMap.get(key);
  5. if (client != null) {
  6. if (!client.isClosed()) {
  7. // 增加引用计数
  8. client.incrementAndGetCount();
  9. return client;
  10. } else {
  11. referenceClientMap.remove(key);
  12. }
  13. }
  14. locks.putIfAbsent(key, new Object());
  15. synchronized (locks.get(key)) {
  16. if (referenceClientMap.containsKey(key)) {
  17. return referenceClientMap.get(key);
  18. }
  19. // 创建 ExchangeClient 客户端
  20. ExchangeClient exchangeClient = initClient(url);
  21. // 将 ExchangeClient 实例传给 ReferenceCountExchangeClient,这里使用了装饰模式
  22. client = new ReferenceCountExchangeClient(exchangeClient, ghostClientMap);
  23. referenceClientMap.put(key, client);
  24. ghostClientMap.remove(key);
  25. locks.remove(key);
  26. return client;
  27. }
  28. }

上面方法先访问缓存,若缓存未命中,则通过 initClient 方法创建新的 ExchangeClient 实例,并将该实例传给 ReferenceCountExchangeClient 构造方法创建一个带有引用计数功能的 ExchangeClient 实例。ReferenceCountExchangeClient 内部实现比较简单,就不分析了。下面我们再来看一下 initClient 方法的代码。

  1. private ExchangeClient initClient(URL url) {
  2. // 获取客户端类型,默认为 netty
  3. String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));
  4. // 添加编解码和心跳包参数到 url 中
  5. url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
  6. url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
  7. // 检测客户端类型是否存在,不存在则抛出异常
  8. if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
  9. throw new RpcException("Unsupported client type: ...");
  10. }
  11. ExchangeClient client;
  12. try {
  13. // 获取 lazy 配置,并根据配置值决定创建的客户端类型
  14. if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
  15. // 创建懒加载 ExchangeClient 实例
  16. client = new LazyConnectExchangeClient(url, requestHandler);
  17. } else {
  18. // 创建普通 ExchangeClient 实例
  19. client = Exchangers.connect(url, requestHandler);
  20. }
  21. } catch (RemotingException e) {
  22. throw new RpcException("Fail to create remoting client for service...");
  23. }
  24. return client;
  25. }

initClient 方法首先获取用户配置的客户端类型,默认为 netty。然后检测用户配置的客户端类型是否存在,不存在则抛出异常。最后根据 lazy 配置决定创建什么类型的客户端。这里的 LazyConnectExchangeClient 代码并不是很复杂,该类会在 request 方法被调用时通过 Exchangers 的 connect 方法创建 ExchangeClient 客户端,该类的代码本节就不分析了。下面我们分析一下 Exchangers 的 connect 方法。

  1. public static ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
  2. if (url == null) {
  3. throw new IllegalArgumentException("url == null");
  4. }
  5. if (handler == null) {
  6. throw new IllegalArgumentException("handler == null");
  7. }
  8. url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
  9. // 获取 Exchanger 实例,默认为 HeaderExchangeClient
  10. return getExchanger(url).connect(url, handler);
  11. }

如上,getExchanger 会通过 SPI 加载 HeaderExchangeClient 实例,这个方法比较简单,大家自己看一下吧。接下来分析 HeaderExchangeClient 的实现。

  1. public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
  2. // 这里包含了多个调用,分别如下:
  3. // 1. 创建 HeaderExchangeHandler 对象
  4. // 2. 创建 DecodeHandler 对象
  5. // 3. 通过 Transporters 构建 Client 实例
  6. // 4. 创建 HeaderExchangeClient 对象
  7. return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
  8. }

这里的调用比较多,我们这里重点看一下 Transporters 的 connect 方法。如下:

  1. public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException {
  2. if (url == null) {
  3. throw new IllegalArgumentException("url == null");
  4. }
  5. ChannelHandler handler;
  6. if (handlers == null || handlers.length == 0) {
  7. handler = new ChannelHandlerAdapter();
  8. } else if (handlers.length == 1) {
  9. handler = handlers[0];
  10. } else {
  11. // 如果 handler 数量大于1,则创建一个 ChannelHandler 分发器
  12. handler = new ChannelHandlerDispatcher(handlers);
  13. }
  14. // 获取 Transporter 自适应拓展类,并调用 connect 方法生成 Client 实例
  15. return getTransporter().connect(url, handler);
  16. }

如上,getTransporter 方法返回的是自适应拓展类,该类会在运行时根据客户端类型加载指定的 Transporter 实现类。若用户未配置客户端类型,则默认加载 NettyTransporter,并调用该类的 connect 方法。如下:

  1. public Client connect(URL url, ChannelHandler listener) throws RemotingException {
  2. // 创建 NettyClient 对象
  3. return new NettyClient(url, listener);
  4. }

Dubbo 服务引入-Version2.7.5的更多相关文章

  1. Dubbo 服务导出-Version2.7.5

    1.源码分析 1.1分析服务导出入口 当容器为spring是dubbo会为容器注册两个监听器:DubboLifecycleComponentApplicationListener和DubboBoots ...

  2. dubbo源码阅读之服务引入

    服务引入 服务引入使用reference标签来对要引入的服务进行配置,包括服务的接口 ,名称,init,check等等配置属性. 在DubboNamespaceHandler中,我们可以看到refer ...

  3. dubbo服务提供与消费

    一.前言 项目中用到了Dubbo,临时抱大腿,学习了dubbo的简单实用方法.现在就来总结一下dubbo如何提供服务,如何消费服务,并做了一个简单的demo作为参考. 二.Dubbo是什么 Dubbo ...

  4. dubbo服务自动化测试搭建

    java实现dubbo的消费者服务编写:ruby实现消费者服务的接口测试:通过消费者间接测试dubbo服务接口的逻辑 内容包括:dubbo服务本地调用环境搭建,dubbo服务启动,消费者部署,脚本编写 ...

  5. 跟我学习dubbo-使用Maven构建Dubbo服务的可执行jar包(4)

    Dubbo服务的运行方式: 1.使用Servlet容器运行(Tomcat.Jetty等)----不可取 缺点:增加复杂性(端口.管理) 浪费资源(内存) 官方:服务容器是一个standalone的启动 ...

  6. 基于注解的Dubbo服务配置

      基于注解的Dubbo服务配置可以大大减少dubbo xml配置文件中的Service配置量,主要步骤如下:   一.服务提供方   1. Dubbo配置文件中增加Dubbo注解扫描 <!-- ...

  7. Dubbo-使用Maven构建Dubbo服务的可执行jar包

    一.为什么要构建Dubbo服务的可执行jar包? 1.1 Dubbo服务运行方式比较 ✎使用Servlet容器运行(Tomcat.Jetty等)  ---不可取 --缺点:增加复杂性(多了容器的端口) ...

  8. Dubbo学习笔记2:Dubbo服务提供端与消费端应用的搭建

    Demo结构介绍 Demo使用Maven聚合功能,里面有三个模块,目录如下: 其中Consumer模块为服务消费者,里面TestConsumer和consumer.xml组成了基于Spring配置方式 ...

  9. 基于Spring开发的DUBBO服务接口测试

    基于Spring开发的DUBBO服务接口测试 知识共享主要内容: 1. Dubbo相关概念和架构,以及dubbo服务程序开发步骤. 2. 基于Spring开发框架的dubbo服务接口测试相关配置. 3 ...

随机推荐

  1. docker启动服务---------------mysql

    1.查找镜像: docker search mysql 也可以去官网查看镜像tag,选择自己需要的版本,否则会下载最新版本:https://hub.docker.com/_/mysql/ 2.下载镜像 ...

  2. sql 删除所有存储过程

    1.执行以下sql语句即可删除所有存储过程 --/**********删除所有存储过程*************************/-- use 数据库名 go declare @tname v ...

  3. Halcon软件介绍与图像基本知识

    1.halcon环境 halcon功能:1.视觉算法(核心)基本 2. 弱语言 3.解释性语言 halcon软件介绍: 1.标题栏 2.菜单栏 3.工具栏 4.工作区 图形窗口(显示图像) 变量窗口( ...

  4. Flink on Yarn三部曲之二:部署和设置

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  5. influxdb集群部署

    环境准备 influxdb enterprise运行条件最低需要三个meta nodes节点以及两个data nodes Meta nodes之间使用TCP和Raft一致性协议通信,默认端口为8089 ...

  6. python识别视频黑屏或者低清晰度

    第一步:获取视频第一帧图片 https://www.cnblogs.com/pythonywy/p/13749735.html 第二步:进行识别 import os import numpy as n ...

  7. 【Azure Developer】使用.Net Core解析JSON的笔记

    在C#中解析JSON的一些历史代码记录,分别记录针对各种情况的解析方式. DLL的引用 using Newtonsoft.Json; using Newtonsoft.Json.Linq; 需要使用的 ...

  8. Java数据结构-01顺序表

    一.定义 线性表是一种线性结构,它是具有相同类型的n(n≥0)个数据元素组成的有限序列. 二.存储分类 1.顺序存储: ①简述:是指将线性表中的各个元素依次存放在一组地址连续的存储单元中,通常将这种方 ...

  9. DTU是怎么与PLC连接通信的

    数据采集是生产制造中最实际最频繁的需求,不管智能设备制造发展到何种程度它都是工业4.0的先决条件,也在数字化工厂当中,工人更多地是处理异常情况,调整设备.但数据采集一直是困扰着所有制造工厂的传统痛点, ...

  10. 通过Azure bot framework composer 设计一个AI对话机器人bot(查询天气)

    本文介绍通过机器人框架设计器 (Bot framework composer)接近拖拉拽的方式设计一个聊天机器人,该聊天机器人的主要功能是发起http请求查询天气.当然,稍微变通下,可以用来查询几乎任 ...