1.源码分析

1.1分析服务导出入口

当容器为spring是dubbo会为容器注册两个监听器:DubboLifecycleComponentApplicationListenerDubboBootstrapApplicationListener。第一个监听器主要负责从容器中获取org.apache.dubbo.common.context.Lifecycle的实现类并调用start()方法

  1. @Override
  2. protected void onApplicationContextEvent(ApplicationContextEvent event) {
  3. if (event instanceof ContextRefreshedEvent) {
  4. onContextRefreshedEvent((ContextRefreshedEvent) event);
  5. } else if (event instanceof ContextClosedEvent) {
  6. onContextClosedEvent((ContextClosedEvent) event);
  7. }
  8. }
  9. protected void onContextRefreshedEvent(ContextRefreshedEvent event) {
  10. initLifecycleComponents(event);
  11. startLifecycleComponents();
  12. }
  13. private void loadLifecycleComponents(List<Lifecycle> lifecycleComponents, ApplicationContext context) {
  14. lifecycleComponents.addAll(beansOfTypeIncludingAncestors(context, Lifecycle.class).values());
  15. }
  16. private void startLifecycleComponents() {
  17. lifecycleComponents.forEach(Lifecycle::start);
  18. }

第二个监听器负责启动dubboBootstrap

  1. @Override
  2. public void onApplicationContextEvent(ApplicationContextEvent event) {
  3. if (event instanceof ContextRefreshedEvent) {
  4. onContextRefreshedEvent((ContextRefreshedEvent) event);
  5. } else if (event instanceof ContextClosedEvent) {
  6. onContextClosedEvent((ContextClosedEvent) event);
  7. }
  8. }
  9. private void onContextRefreshedEvent(ContextRefreshedEvent event) {
  10. dubboBootstrap.start();
  11. }

这个start()方法最终会调用获取到所有的ServiceBean对象并挨个export,执行链如下

  1. org.apache.dubbo.config.bootstrap.DubboBootstrap#start
  2. org.apache.dubbo.config.bootstrap.DubboBootstrap#exportServices
  3. private void exportServices() {
  4. configManager.getServices().forEach(sc -> {
  5. // TODO, compatible with ServiceConfig.export()
  6. ServiceConfig serviceConfig = (ServiceConfig) sc;
  7. serviceConfig.setBootstrap(this);
  8. if (exportAsync) {
  9. ExecutorService executor = executorRepository.getServiceExporterExecutor();
  10. Future<?> future = executor.submit(() -> {
  11. //服务开始导出
  12. sc.export();
  13. });
  14. asyncExportingFutures.add(future);
  15. } else {
  16. sc.export();
  17. exportedServices.add(sc);
  18. }
  19. });
  20. }
  1. //这个方法从整体上来看就做了三件事
  2. //①实例并初始化bootstrap
  3. //②对当前对象做属性的检查和赋值
  4. //③调用doExport()方法
  5. public synchronized void export() {
  6. if (!shouldExport()) {
  7. return;
  8. }
  9. if (bootstrap == null) {
  10. bootstrap = DubboBootstrap.getInstance();
  11. bootstrap.init();
  12. }
  13. checkAndUpdateSubConfigs();
  14. //init serviceMetadata
  15. serviceMetadata.setVersion(version);
  16. serviceMetadata.setGroup(group);
  17. serviceMetadata.setDefaultGroup(group);
  18. serviceMetadata.setServiceType(getInterfaceClass());
  19. serviceMetadata.setServiceInterfaceName(getInterface());
  20. serviceMetadata.setTarget(getRef());
  21. if (shouldDelay()) {
  22. DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
  23. } else {
  24. doExport();
  25. }
  26. }

1.2 检查配置

先看checkAndUpdateSubConfigs()这个方法中具体做了什么事

  1. private void checkAndUpdateSubConfigs() {
  2. // Use default configs defined explicitly with global scope
  3. //用于检测 provider、application 等核心配置类对象是否为空,
  4. // 若为空,则尝试从其他配置类对象中获取相应的实例。
  5. completeCompoundConfigs();
  6. //检测provider属性是否为空,若为空则new一个ProviderConfig并refresh
  7. checkDefault();
  8. //检测protocol属性
  9. checkProtocol();
  10. // if protocol is not injvm checkRegistry
  11. //检测protocols数量是否为1,并且名字是injvm
  12. if (!isOnlyInJvm()) {
  13. checkRegistry();
  14. }
  15. this.refresh();
  16. if (StringUtils.isEmpty(interfaceName)) {
  17. throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
  18. }
  19. // 检测 ref 是否为泛化服务类型
  20. if (ref instanceof GenericService) {
  21. // 设置 interfaceClass 为 GenericService.class
  22. interfaceClass = GenericService.class;
  23. if (StringUtils.isEmpty(generic)) {
  24. // 设置 generic = "true"
  25. generic = Boolean.TRUE.toString();
  26. }
  27. } else {
  28. // ref 非 GenericService 类型
  29. try {
  30. // 对 interfaceClass,以及 <dubbo:method> 标签中的必要字段进行检查
  31. interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
  32. .getContextClassLoader());
  33. } catch (ClassNotFoundException e) {
  34. throw new IllegalStateException(e.getMessage(), e);
  35. }
  36. // 对 ref 合法性进行检测
  37. checkInterfaceAndMethods(interfaceClass, getMethods());
  38. checkRef();
  39. generic = Boolean.FALSE.toString();
  40. }
  41. // local 和 stub 在功能应该是一致的,用于配置本地存根
  42. if (local != null) {
  43. if ("true".equals(local)) {
  44. local = interfaceName + "Local";
  45. }
  46. Class<?> localClass;
  47. try {
  48. localClass = ClassUtils.forNameWithThreadContextClassLoader(local);
  49. } catch (ClassNotFoundException e) {
  50. throw new IllegalStateException(e.getMessage(), e);
  51. }
  52. if (!interfaceClass.isAssignableFrom(localClass)) {
  53. throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
  54. }
  55. }
  56. if (stub != null) {
  57. if ("true".equals(stub)) {
  58. stub = interfaceName + "Stub";
  59. }
  60. Class<?> stubClass;
  61. try {
  62. stubClass = ClassUtils.forNameWithThreadContextClassLoader(stub);
  63. } catch (ClassNotFoundException e) {
  64. throw new IllegalStateException(e.getMessage(), e);
  65. }
  66. if (!interfaceClass.isAssignableFrom(stubClass)) {
  67. throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
  68. }
  69. }
  70. checkStubAndLocal(interfaceClass);
  71. // 检测各种对象是否为空,为空则新建,或者抛出异常
  72. ConfigValidationUtils.checkMock(interfaceClass, this);
  73. ConfigValidationUtils.validateServiceConfig(this);
  74. //动态添加一些参数(通过SPI机制调用org.apache.dubbo.config.AppendParametersComponent实现类并调用appendExportParameters方法将,ServiceConfig对象传入)
  75. appendParameters();
  76. }

下面对配置检查的逻辑进行简单的总结,如下:

  1. 检测 <dubbo:service> 标签的 interface 属性合法性,不合法则抛出异常
  2. 检测 ProviderConfigApplicationConfig 等核心配置类对象是否为空,若为空,则尝试从其他配置类对象中获取相应的实例。
  3. 检测并处理泛化服务和普通服务类
  4. 检测本地存根配置,并进行相应的处理
  5. ApplicationConfigRegistryConfig等配置类进行检测,为空则尝试创建,若无法创建则抛出异常
  1. //dispathch方法暂时不做分析
  2. protected synchronized void doExport() {
  3. if (unexported) {
  4. throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
  5. }
  6. if (exported) {
  7. return;
  8. }
  9. exported = true;
  10. if (StringUtils.isEmpty(path)) {
  11. path = interfaceName;
  12. }
  13. doExportUrls();
  14. // dispatch a ServiceConfigExportedEvent since 2.7.4
  15. dispatch(new ServiceConfigExportedEvent(this));
  16. }

1.3多协议多注册中心导出服务

  1. private void doExportUrls() {
  2. //首先将服务导入本地
  3. ServiceRepository repository = ApplicationModel.getServiceRepository();
  4. ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass());
  5. repository.registerProvider(
  6. getUniqueServiceName(),
  7. ref,
  8. serviceDescriptor,
  9. this,
  10. serviceMetadata
  11. );
  12. // 加载注册中心链接
  13. List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
  14. // 遍历 protocols,并在每个协议下导出服务
  15. for (ProtocolConfig protocolConfig : protocols) {
  16. String pathKey = URL.buildKey(getContextPath(protocolConfig)
  17. .map(p -> p + "/" + path)
  18. .orElse(path), group, version);
  19. // In case user specified path, register service one more time to map it to path.
  20. repository.registerService(pathKey, interfaceClass);
  21. // TODO, uncomment this line once service key is unified
  22. serviceMetadata.setServiceKey(pathKey);
  23. doExportUrlsFor1Protocol(protocolConfig, registryURLs);
  24. }
  25. }

  1. public static List<URL> loadRegistries(AbstractInterfaceConfig interfaceConfig, boolean provider) {
  2. // check && override if necessary
  3. List<URL> registryList = new ArrayList<URL>();
  4. //获取全局配置
  5. ApplicationConfig application = interfaceConfig.getApplication();
  6. //获取注册中心配置集合
  7. List<RegistryConfig> registries = interfaceConfig.getRegistries();
  8. if (CollectionUtils.isNotEmpty(registries)) {
  9. for (RegistryConfig config : registries) {
  10. String address = config.getAddress();
  11. if (StringUtils.isEmpty(address)) {
  12. address = ANYHOST_VALUE;
  13. }
  14. if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
  15. Map<String, String> map = new HashMap<String, String>();
  16. // 添加 ApplicationConfig 中的字段信息到 map 中
  17. AbstractConfig.appendParameters(map, application);
  18. // 添加 RegistryConfig 字段信息到 map 中
  19. AbstractConfig.appendParameters(map, config);
  20. // 添加 path、pid,protocol 等信息到 map 中
  21. map.put(PATH_KEY, RegistryService.class.getName());
  22. AbstractInterfaceConfig.appendRuntimeParameters(map);
  23. if (!map.containsKey(PROTOCOL_KEY)) {
  24. map.put(PROTOCOL_KEY, DUBBO_PROTOCOL);
  25. }
  26. // 解析得到 URL 列表,address 可能包含多个注册中心 ip,
  27. // 因此解析得到的是一个 URL 列表
  28. List<URL> urls = UrlUtils.parseURLs(address, map);
  29. for (URL url : urls) {
  30. url = URLBuilder.from(url)
  31. .addParameter(REGISTRY_KEY, url.getProtocol())
  32. .setProtocol(extractRegistryType(url))
  33. .build();
  34. if ((provider && url.getParameter(REGISTER_KEY, true))
  35. || (!provider && url.getParameter(SUBSCRIBE_KEY, true))) {
  36. registryList.add(url);
  37. }
  38. }
  39. }
  40. }
  41. }
  42. return registryList;
  43. }

loadRegistries 方法主要包含如下的逻辑:

  1. 构建参数映射集合,也就是 map
  2. 构建注册中心链接列表
  3. 遍历链接列表,并根据条件决定是否将其添加到 registryList

1.4组装 URL

配置检查完毕后,紧接着要做的事情是根据配置,以及其他一些信息组装 URL。URL 是 Dubbo 配置的载体,通过 URL 可让 Dubbo 的各种配置在各个模块之间传递。URL 之于 Dubbo,犹如水之于鱼,非常重要。大家在阅读 Dubbo 服务导出相关源码的过程中,要注意 URL 内容的变化。既然 URL 如此重要,那么下面我们来了解一下 URL 组装的过程。

  1. private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
  2. String name = protocolConfig.getName();
  3. // 如果协议名为空,或空串,则将协议名变量设置为 dubbo
  4. if (StringUtils.isEmpty(name)) {
  5. name = DUBBO;
  6. }
  7. Map<String, String> map = new HashMap<String, String>();
  8. map.put(SIDE_KEY, PROVIDER_SIDE);
  9. // 通过反射将对象的字段信息添加到 map 中
  10. ServiceConfig.appendRuntimeParameters(map);
  11. AbstractConfig.appendParameters(map, getMetrics());
  12. AbstractConfig.appendParameters(map, getApplication());
  13. AbstractConfig.appendParameters(map, getModule());
  14. // remove 'default.' prefix for configs from ProviderConfig
  15. // appendParameters(map, provider, Constants.DEFAULT_KEY);
  16. AbstractConfig.appendParameters(map, provider);
  17. AbstractConfig.appendParameters(map, protocolConfig);
  18. AbstractConfig.appendParameters(map, this);
  19. // methods 为 MethodConfig 集合,MethodConfig 中存储了 <dubbo:method> 标签的配置信息
  20. //由于太繁琐了就是配置标签的解析就跳过
  21. if (CollectionUtils.isNotEmpty(getMethods())) {
  22. for (MethodConfig method : getMethods()) {
  23. AbstractConfig.appendParameters(map, method, method.getName());
  24. String retryKey = method.getName() + ".retry";
  25. if (map.containsKey(retryKey)) {
  26. String retryValue = map.remove(retryKey);
  27. if ("false".equals(retryValue)) {
  28. map.put(method.getName() + ".retries", "0");
  29. }
  30. }
  31. List<ArgumentConfig> arguments = method.getArguments();
  32. if (CollectionUtils.isNotEmpty(arguments)) {
  33. for (ArgumentConfig argument : arguments) {
  34. // convert argument type
  35. if (argument.getType() != null && argument.getType().length() > 0) {
  36. Method[] methods = interfaceClass.getMethods();
  37. // visit all methods
  38. if (methods != null && methods.length > 0) {
  39. for (int i = 0; i < methods.length; i++) {
  40. String methodName = methods[i].getName();
  41. // target the method, and get its signature
  42. if (methodName.equals(method.getName())) {
  43. Class<?>[] argtypes = methods[i].getParameterTypes();
  44. // one callback in the method
  45. if (argument.getIndex() != -1) {
  46. if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
  47. AbstractConfig.appendParameters(map, argument, method.getName() + "." + argument.getIndex());
  48. } else {
  49. throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
  50. }
  51. } else {
  52. // multiple callbacks in the method
  53. for (int j = 0; j < argtypes.length; j++) {
  54. Class<?> argclazz = argtypes[j];
  55. if (argclazz.getName().equals(argument.getType())) {
  56. AbstractConfig.appendParameters(map, argument, method.getName() + "." + j);
  57. if (argument.getIndex() != -1 && argument.getIndex() != j) {
  58. throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
  59. }
  60. }
  61. }
  62. }
  63. }
  64. }
  65. }
  66. } else if (argument.getIndex() != -1) {
  67. AbstractConfig.appendParameters(map, argument, method.getName() + "." + argument.getIndex());
  68. } else {
  69. throw new IllegalArgumentException("Argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
  70. }
  71. }
  72. }
  73. } // end of methods for
  74. }
  75. // 检测 generic 是否为 "true",并根据检测结果向 map 中添加不同的信息
  76. if (ProtocolUtils.isGeneric(generic)) {
  77. map.put(GENERIC_KEY, generic);
  78. map.put(METHODS_KEY, ANY_VALUE);
  79. } else {
  80. String revision = Version.getVersion(interfaceClass, version);
  81. if (revision != null && revision.length() > 0) {
  82. map.put(REVISION_KEY, revision);
  83. }
  84. // 为接口生成包裹类 Wrapper,Wrapper 中包含了接口的详细信息,比如接口方法名数组,字段信息等
  85. String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
  86. if (methods.length == 0) {
  87. logger.warn("No method found in service interface " + interfaceClass.getName());
  88. map.put(METHODS_KEY, ANY_VALUE);
  89. } else {
  90. // 将逗号作为分隔符连接方法名,并将连接后的字符串放入 map 中
  91. map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
  92. }
  93. }
  94. // 添加 token 到 map 中
  95. if (!ConfigUtils.isEmpty(token)) {
  96. if (ConfigUtils.isDefault(token)) {
  97. map.put(TOKEN_KEY, UUID.randomUUID().toString());
  98. } else {
  99. map.put(TOKEN_KEY, token);
  100. }
  101. }
  102. //init serviceMetadata attachments
  103. serviceMetadata.getAttachments().putAll(map);
  104. // export service
  105. // 获取 host 和 port
  106. String host = findConfigedHosts(protocolConfig, registryURLs, map);
  107. Integer port = findConfigedPorts(protocolConfig, name, map);
  108. // 组装 URL
  109. URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
  110. .....省略
  111. }

1.5导出 Dubbo 服务

  1. private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
  2. ...省略
  3. // You can customize Configurator to append extra parameters
  4. if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
  5. .hasExtension(url.getProtocol())) {
  6. // 加载 ConfiguratorFactory,并生成 Configurator 实例,然后通过实例配置 url
  7. url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
  8. .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
  9. }
  10. String scope = url.getParameter(SCOPE_KEY);
  11. // don't export when none is configured
  12. // 如果 scope = none,则什么都不做
  13. if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
  14. // export to local if the config is not remote (export to remote only when config is remote)
  15. // scope != remote,导出到本地
  16. if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
  17. exportLocal(url);
  18. }
  19. // export to remote if the config is not local (export to local only when config is local)
  20. // scope != local,导出到远程
  21. if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
  22. if (CollectionUtils.isNotEmpty(registryURLs)) {
  23. for (URL registryURL : registryURLs) {
  24. //if protocol is only injvm ,not register
  25. if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
  26. continue;
  27. }
  28. url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
  29. // 加载监视器链接
  30. URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);
  31. if (monitorUrl != null) {
  32. // 将监视器链接作为参数添加到 url 中
  33. url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
  34. }
  35. if (logger.isInfoEnabled()) {
  36. if (url.getParameter(REGISTER_KEY, true)) {
  37. logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
  38. } else {
  39. logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
  40. }
  41. }
  42. // For providers, this is used to enable custom proxy to generate invoker
  43. String proxy = url.getParameter(PROXY_KEY);
  44. if (StringUtils.isNotEmpty(proxy)) {
  45. registryURL = registryURL.addParameter(PROXY_KEY, proxy);
  46. }
  47. // 为服务提供类(ref)生成 Invoker
  48. Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
  49. // DelegateProviderMetaDataInvoker 用于持有 Invoker 和 ServiceConfig
  50. DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
  51. // 导出服务,并生成 Exporter
  52. Exporter<?> exporter = protocol.export(wrapperInvoker);
  53. exporters.add(exporter);
  54. }
  55. } else {
  56. // 不存在注册中心,仅导出服务
  57. if (logger.isInfoEnabled()) {
  58. logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
  59. }
  60. Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
  61. DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
  62. Exporter<?> exporter = protocol.export(wrapperInvoker);
  63. exporters.add(exporter);
  64. }
  65. /**
  66. * @since 2.7.0
  67. * ServiceData Store
  68. */
  69. WritableMetadataService metadataService = WritableMetadataService.getExtension(url.getParameter(METADATA_KEY, DEFAULT_METADATA_STORAGE_TYPE));
  70. if (metadataService != null) {
  71. metadataService.publishServiceDefinition(url);
  72. }
  73. }
  74. }
  75. this.urls.add(url);
  76. }

上面代码根据 url 中的 scope 参数决定服务导出方式,分别如下:

  • scope = none,不导出服务
  • scope != remote,导出到本地
  • scope != local,导出到远程

不管是导出到本地,还是远程。进行服务导出之前,均需要先创建 Invoker,这是一个很重要的步骤。因此下面先来分析 Invoker 的创建过程。


1.4 Invoker 创建过程

在 Dubbo 中,Invoker 是一个非常重要的模型。在服务提供端,以及服务引用端均会出现 Invoker。Dubbo 官方文档中对 Invoker 进行了说明,这里引用一下。

Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。

既然 Invoker 如此重要,那么我们很有必要搞清楚 Invoker 的用途。Invoker 是由 ProxyFactory 创建而来,Dubbo 默认的 ProxyFactory 实现类是 JavassistProxyFactory。下面我们到 JavassistProxyFactory 代码中,探索 Invoker 的创建过程。如下:

  1. @Override
  2. public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
  3. // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
  4. // 为目标类创建 Wrapper
  5. final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
  6. // 创建匿名 Invoker 类对象,并实现 doInvoke 方法。
  7. return new AbstractProxyInvoker<T>(proxy, type, url) {
  8. @Override
  9. protected Object doInvoke(T proxy, String methodName,
  10. Class<?>[] parameterTypes,
  11. Object[] arguments) throws Throwable {
  12. return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
  13. }
  14. };
  15. }
  1. //
  2. // Source code recreated from a .class file by IntelliJ IDEA
  3. // (powered by Fernflower decompiler)
  4. //
  5. //这是通过javassist生成的接口实现的包装类(通过idea反编译过后)
  6. package org.apache.dubbo.common.bytecode;
  7. import java.lang.reflect.InvocationTargetException;
  8. import java.util.Map;
  9. import org.apache.dubbo.common.bytecode.ClassGenerator.DC;
  10. import org.apache.dubbo.demo.GreetingService;
  11. public class Wrapper0 extends Wrapper implements DC {
  12. //属性名称数组
  13. public static String[] pns;
  14. // pts 用于存储成员变量名和类型
  15. public static Map pts;
  16. // mns 为方法名列表
  17. public static String[] mns;
  18. // dmns 用于存储“定义在当前类中的方法”的名称
  19. public static String[] dmns;
  20. public static Class[] mts0;
  21. public String[] getPropertyNames() {
  22. return pns;
  23. }
  24. public boolean hasProperty(String var1) {
  25. return pts.containsKey(var1);
  26. }
  27. public Class getPropertyType(String var1) {
  28. return (Class)pts.get(var1);
  29. }
  30. public String[] getMethodNames() {
  31. return mns;
  32. }
  33. public String[] getDeclaredMethodNames() {
  34. return dmns;
  35. }
  36. public void setPropertyValue(Object var1, String var2, Object var3) {
  37. try {
  38. GreetingService var4 = (GreetingService)var1;
  39. } catch (Throwable var6) {
  40. throw new IllegalArgumentException(var6);
  41. }
  42. throw new NoSuchPropertyException("Not found property \"" + var2 + "\" field or setter method in class org.apache.dubbo.demo.GreetingService.");
  43. }
  44. public Object getPropertyValue(Object var1, String var2) {
  45. try {
  46. GreetingService var3 = (GreetingService)var1;
  47. } catch (Throwable var5) {
  48. throw new IllegalArgumentException(var5);
  49. }
  50. throw new NoSuchPropertyException("Not found property \"" + var2 + "\" field or setter method in class org.apache.dubbo.demo.GreetingService.");
  51. }
  52. public Object invokeMethod(Object var1, String var2, Class[] var3, Object[] var4) throws InvocationTargetException {
  53. GreetingService var5;
  54. try {
  55. var5 = (GreetingService)var1;
  56. } catch (Throwable var8) {
  57. throw new IllegalArgumentException(var8);
  58. }
  59. try {
  60. if ("hello".equals(var2) && var3.length == 0) {
  61. return var5.hello();
  62. }
  63. } catch (Throwable var9) {
  64. throw new InvocationTargetException(var9);
  65. }
  66. throw new NoSuchMethodException("Not found method \"" + var2 + "\" in class org.apache.dubbo.demo.GreetingService.");
  67. }
  68. public Wrapper0() {
  69. }
  70. }

1.7导出服务到本地

本节我们来看一下服务导出相关的代码,按照代码执行顺序,本节先来分析导出服务到本地的过程。相关代码如下:

  1. private void exportLocal(URL url) {
  2. //构建injvm:127.0.0.1...
  3. //并在本地导出服务
  4. URL local = URLBuilder.from(url)
  5. .setProtocol(LOCAL_PROTOCOL)
  6. .setHost(LOCALHOST_VALUE)
  7. .setPort(0)
  8. .build();
  9. Exporter<?> exporter = protocol.export(
  10. PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local));
  11. exporters.add(exporter);
  12. logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local);
  13. }

1.8导出服务到远程

与导出服务到本地相比,导出服务到远程的过程要复杂不少,其包含了服务导出与服务注册两个过程。这两个过程涉及到了大量的调用,比较复杂。按照代码执行顺序,本节先来分析服务导出逻辑,服务注册逻辑将在下一节进行分析。下面开始分析,我们把目光移动到 RegistryProtocol 的 export 方法上。

  1. public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
  2. //尝试将url中的协议替换成映射register的值,并删除register
  3. //zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&export=dubbo%3A%2F%2F192.168.1.6%3A20880%2Forg.apache.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26bind.ip%3D192.168.1.6%26bind.port%3D20880%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.demo.DemoService%26metadata-type%3Dremote%26methods%3DsayHello%2CsayHelloAsync%26pid%3D3972%26qos.port%3D22222%26release%3D%26side%3Dprovider%26timestamp%3D1585222934666&metadata-type=remote&pid=3972&qos.port=22222&timestamp=1585222934636
  4. URL registryUrl = getRegistryUrl(originInvoker);
  5. //获取映射export的值并解析成url
  6. URL providerUrl = getProviderUrl(originInvoker);
  7. //将providerUrl的协议修改为provider并添加category和check两个属性
  8. final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
  9. // 创建监听器
  10. final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
  11. overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
  12. providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
  13. //export invoker
  14. final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
  15. // 根据 URL 加载 Registry 实现类,比如 ZookeeperRegistry
  16. final Registry registry = getRegistry(originInvoker);
  17. // 获取已注册的服务提供者 URL,比如:
  18. final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);
  19. // 获取 register 参数
  20. boolean register = providerUrl.getParameter(REGISTER_KEY, true);
  21. // 根据 register 的值决定是否注册服务
  22. if (register) {
  23. register(registryUrl, registeredProviderUrl);
  24. }
  25. // 向注册中心进行订阅 override 数据
  26. registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
  27. exporter.setRegisterUrl(registeredProviderUrl);
  28. exporter.setSubscribeUrl(overrideSubscribeUrl);
  29. notifyExport(exporter);
  30. // 创建并返回 DestroyableExporter
  31. return new DestroyableExporter<>(exporter);
  32. }

上面代码看起来比较复杂,主要做如下一些操作:

  1. 调用 doLocalExport 导出服务
  2. 向注册中心注册服务
  3. 向注册中心进行订阅 override 数据
  4. 创建并返回 DestroyableExporter

在以上操作中,除了创建并返回 DestroyableExporter 没什么难度外,其他几步操作都不是很简单。这其中,导出服务和注册服务是本章要重点分析的逻辑。 订阅 override 数据并非本文重点内容,后面会简单介绍一下。下面先来分析 doLocalExport 方法的逻辑,如下:

  1. private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
  2. String key = getCacheKey(originInvoker);
  3. //若缓存中没有与key映射的value则执行lambda表达式并将key与lambda表达式返回值做映射
  4. return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
  5. // 创建 Invoker 为委托类对象
  6. Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
  7. // 调用 protocol 的 export 方法导出服务
  8. return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
  9. });
  10. }

上面的protocol首先会通过dubbo的SPI机制依次执行Protocol的wapper实现类的export方法(构建protocol的Filter链和listener链),最终会走到invokerDelegate中url的协议的实现类(本例中为dubbo)

  1. public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
  2. URL url = invoker.getUrl();
  3. // 获取服务标识,理解成服务坐标也行。由服务组名,服务名,服务版本号以及端口组成。比如:
  4. // demoGroup/com.alibaba.dubbo.demo.DemoService:1.0.1:20880
  5. String key = serviceKey(url);
  6. // 创建 DubboExporter
  7. DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
  8. // 将 <key, exporter> 键值对放入缓存中
  9. exporterMap.put(key, exporter);
  10. // 本地存根相关代码
  11. Boolean isStubSupportEvent = url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT);
  12. Boolean isCallbackservice = url.getParameter(IS_CALLBACK_SERVICE, false);
  13. if (isStubSupportEvent && !isCallbackservice) {
  14. String stubServiceMethods = url.getParameter(STUB_EVENT_METHODS_KEY);
  15. if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
  16. if (logger.isWarnEnabled()) {
  17. logger.warn(new IllegalStateException("consumer [" + url.getParameter(INTERFACE_KEY) +
  18. "], has set stubproxy support event ,but no stub methods founded."));
  19. }
  20. }
  21. }
  22. // 启动服务器
  23. openServer(url);
  24. // 优化序列化
  25. optimizeSerialization(url);
  26. return exporter;
  27. }

如上,我们重点关注 DubboExporter 的创建以及 openServer 方法,其他逻辑看不懂也没关系,不影响理解服务导出过程。另外,DubboExporter 的代码比较简单,就不分析了。下面分析 openServer 方法。

  1. private void openServer(URL url) {
  2. // 获取 host:port,并将其作为服务器实例的 key,用于标识当前的服务器实例
  3. String key = url.getAddress();
  4. boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
  5. if (isServer) {
  6. // 访问缓存
  7. ExchangeServer server = serverMap.get(key);
  8. if (server == null) {
  9. // 创建服务器实例
  10. serverMap.put(key, createServer(url));
  11. } else {
  12. // 服务器已创建,则根据 url 中的配置重置服务器
  13. server.reset(url);
  14. }
  15. }
  16. }

如上,在同一台机器上(单网卡),同一个端口上仅允许启动一个服务器实例。若某个端口上已有服务器实例,此时则调用 reset 方法重置服务器的一些配置。接下来分析createServer方法。

  1. private ProtocolServer createServer(URL url) {
  2. url = URLBuilder.from(url)
  3. // send readonly event when server closes, it's enabled by default
  4. .addParameterIfAbsent(CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString())
  5. // 添加心跳检测配置到 url 中
  6. .addParameterIfAbsent(HEARTBEAT_KEY, String.valueOf(DEFAULT_HEARTBEAT))
  7. // 添加编码解码器参数
  8. .addParameter(CODEC_KEY, DubboCodec.NAME)
  9. .build();
  10. String str = url.getParameter(SERVER_KEY, DEFAULT_REMOTING_SERVER);
  11. // 通过 SPI 检测是否存在 server 参数所代表的 Transporter 拓展,不存在则抛出异常
  12. if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
  13. throw new RpcException("Unsupported server type: " + str + ", url: " + url);
  14. }
  15. ExchangeServer server;
  16. try {
  17. // 创建 ExchangeServer
  18. server = Exchangers.bind(url, requestHandler);
  19. } catch (RemotingException e) {
  20. throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
  21. }
  22. // 获取 client 参数,可指定 netty,mina,grizzly
  23. // 同样通过SPI检测是否存在 client 参数所代表的 Transporter 拓展,不存在则抛出异常
  24. str = url.getParameter(CLIENT_KEY);
  25. if (str != null && str.length() > 0) {
  26. Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
  27. if (!supportedTypes.contains(str)) {
  28. throw new RpcException("Unsupported client type: " + str);
  29. }
  30. }
  31. return new DubboProtocolServer(server);
  32. }

如上,createServer 包含三个核心的逻辑。第一是检测是否存在 server 参数所代表的 Transporter 拓展,不存在则抛出异常。第二是创建服务器实例。第三是检测是否支持 client 参数所表示的 Transporter 拓展,不存在也是抛出异常。两次检测操作所对应的代码比较直白了,无需多说。但创建服务器的操作目前还不是很清晰,我们继续往下看。

  1. public static ExchangeServer bind(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,默认为 HeaderExchanger。
  10. // 紧接着调用 HeaderExchanger 的 bind 方法创建 ExchangeServer 实例
  11. return getExchanger(url).bind(url, handler);
  12. }
  1. public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
  2. // 创建 HeaderExchangeServer 实例,该方法包含了多个逻辑,分别如下:
  3. // 1. new HeaderExchangeHandler(handler)
  4. // 2. new DecodeHandler(new HeaderExchangeHandler(handler))
  5. // 3. Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))
  6. return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
  7. }

HeaderExchanger 的 bind 方法包含的逻辑比较多,但目前我们仅需关心 Transporters 的 bind 方法逻辑即可。该方法的代码如下:

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

如上,getTransporter() 方法获取的 Transporter 是在运行时动态创建的,类名为 TransporterAdaptive,也就是自适应拓展类。TransporterAdaptive 会在运行时根据传入的 URL 参数决定加载什么类型的 Transporter,默认为 NettyTransporter。下面我们继续跟下去,这次分析的是 NettyTransporter 的 bind 方法。

  1. public Server bind(URL url, ChannelHandler listener) throws RemotingException {
  2. // 创建 NettyServer
  3. return new NettyServer(url, listener);
  4. }

这里仅有一句创建 NettyServer 的代码,无需多说,我们继续向下看。

  1. public class NettyServer extends AbstractServer implements Server {
  2. public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
  3. // 调用父类构造方法
  4. super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
  5. }
  6. }
  7. public abstract class AbstractServer extends AbstractEndpoint implements Server {
  8. public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {
  9. // 调用父类构造方法,这里就不用跟进去了,没什么复杂逻辑
  10. super(url, handler);
  11. localAddress = getUrl().toInetSocketAddress();
  12. // 获取 ip 和端口
  13. String bindIp = getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost());
  14. int bindPort = getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort());
  15. if (url.getParameter(Constants.ANYHOST_KEY, false) || NetUtils.isInvalidLocalHost(bindIp)) {
  16. // 设置 ip 为 0.0.0.0
  17. bindIp = NetUtils.ANYHOST;
  18. }
  19. bindAddress = new InetSocketAddress(bindIp, bindPort);
  20. // 获取最大可接受连接数
  21. this.accepts = url.getParameter(Constants.ACCEPTS_KEY, Constants.DEFAULT_ACCEPTS);
  22. this.idleTimeout = url.getParameter(Constants.IDLE_TIMEOUT_KEY, Constants.DEFAULT_IDLE_TIMEOUT);
  23. try {
  24. // 调用模板方法 doOpen 启动服务器
  25. doOpen();
  26. } catch (Throwable t) {
  27. throw new RemotingException("Failed to bind ");
  28. }
  29. DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();
  30. executor = (ExecutorService) dataStore.get(Constants.EXECUTOR_SERVICE_COMPONENT_KEY, Integer.toString(url.getPort()));
  31. }
  32. protected abstract void doOpen() throws Throwable;
  33. protected abstract void doClose() throws Throwable;
  34. }

上面代码多为赋值代码,不需要多讲。我们重点关注 doOpen 抽象方法,该方法需要子类实现。下面回到 NettyServer 中。

  1. protected void doOpen() throws Throwable {
  2. NettyHelper.setNettyLoggerFactory();
  3. // 创建 boss 和 worker 线程池
  4. ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", true));
  5. ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true));
  6. ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS));
  7. // 创建 ServerBootstrap
  8. bootstrap = new ServerBootstrap(channelFactory);
  9. final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
  10. channels = nettyHandler.getChannels();
  11. bootstrap.setOption("child.tcpNoDelay", true);
  12. // 设置 PipelineFactory
  13. bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
  14. @Override
  15. public ChannelPipeline getPipeline() {
  16. NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
  17. ChannelPipeline pipeline = Channels.pipeline();
  18. pipeline.addLast("decoder", adapter.getDecoder());
  19. pipeline.addLast("encoder", adapter.getEncoder());
  20. pipeline.addLast("handler", nettyHandler);
  21. return pipeline;
  22. }
  23. });
  24. // 绑定到指定的 ip 和端口上
  25. channel = bootstrap.bind(getBindAddress());
  26. }

以上就是 NettyServer 创建的过程,dubbo 默认使用的 NettyServer 是基于 netty 3.x 版本实现的,比较老了。因此 Dubbo 另外提供了 netty 4.x 版本的 NettyServer,大家可在使用 Dubbo 的过程中按需进行配置。

Dubbo 服务导出-Version2.7.5的更多相关文章

  1. RPC -dubbo 服务导出实现

    在阅读此文章之前,我希望阅读者对Spring 扩展机制的有一定的了解,比如:自定义标签与Spring整合, InitializingBean 接口,ApplicationContextAware,Be ...

  2. Dubbo 服务引入-Version2.7.5

    1.服务引用原理 Dubbo 服务引用的时机有两个,第一个是在 Spring 容器调用 ReferenceBean 的 afterPropertiesSet 方法时引用服务,第二个是在 Referen ...

  3. Dubbo 源码分析 - 服务导出

    1.服务导出过程 本篇文章,我们来研究一下 Dubbo 导出服务的过程.Dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 在接收到事件后,会立即执行服务导出逻辑.整个逻辑大致可 ...

  4. dubbo源码阅读之服务导出

    dubbo服务导出 常见的使用dubbo的方式就是通过spring配置文件进行配置.例如下面这样 <?xml version="1.0" encoding="UTF ...

  5. Dubbo源码阅读-服务导出

    Dubbo服务导出过程始于Spring容器发布刷新事件,Dubbo在接收到事件后,会立即执行服务导出逻辑.整个逻辑大致可分为三个部分,第一部分是前置工作,主要用于检查参数,组装URL.第二部分是导出服 ...

  6. Dubbo源码(三) - 服务导出(生产者)

    前言 本文基于Dubbo2.6.x版本,中文注释版源码已上传github:xiaoguyu/dubbo 在了解了Dubbo SPI后,我们来了解下Dubbo服务导出的过程. Dubbo的配置是通过Du ...

  7. Dubbo源码学习之-服务导出

    前言 忙的时候,会埋怨学习的时间太少,缺少个人的空间,于是会争分夺秒的工作.学习.而一旦繁忙的时候过去,有时间了之后,整个人又会不自觉的陷入一种懒散的状态中,时间也显得不那么重要了,随便就可以浪费掉几 ...

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

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

  9. dubbo系列四、dubbo服务暴露过程源码解析

    一.代码准备 1.示例代码 参考dubbo系列二.dubbo+zookeeper+dubboadmin分布式服务框架搭建(windows平台) 2.简单了解下spring自定义标签 https://w ...

随机推荐

  1. spring boot:用dynamic-datasource-spring-boot-starter配置多数据源访问seata(seata 1.3.0 / spring boot 2.3.3)

    一,dynamic-datasource-spring-boot-starter的优势? 1,dynamic-datasource-spring-boot-starter 是一个基于springboo ...

  2. STM32中断

    STM32的中断分两个类型:内核异常和外部中断. 内核异常不能够被打断,不能被设置优先级(它的优先级是凌驾于外部中断之上的).常见的内核异常有以下几种:复位(reset),不可屏蔽中断(NMI),硬错 ...

  3. MySQL备份和恢复[1]-概述

    备份类型 完全备份,部分备份 完全备份:整个数据集 部分备份:只备份数据子集,如部分库或表 完全备份.增量备份.差异备份 增量备份:仅备份最近一次完全备份或增量备份(如果存在增量)以来变化的数据,备份 ...

  4. 微信小程序-基于高德地图API实现天气组件(动态效果)

    微信小程序-基于高德地图API实现天气组件(动态效果) ​ 在社区翻腾了许久,没有找到合适的天气插件.迫不得已,只好借鉴互联网上的web项目,手动迁移到小程序中使用.现在分享到互联网社区中,帮助后续有 ...

  5. SpringMVC异常的处理机制

    SpringMVC异常的处理机制 处理流程图 其本质还是把异常交给SpringMVC框架来处理 系统的dao.service.controller出现异常都通过throws Exception向上抛出 ...

  6. 基于web的图书管理系统设计与实现(附演示地址)

    欢迎访问博主个人网站,记得收藏哦,点击查看 - - - >>>> 公众号推荐:计算机类毕业设计系统源码,IT技术文章分享,游戏源码,网页模板 小程序推荐:网站资源快速收录--百 ...

  7. 开发笔记:PDF生成文字和图片水印

    背景 团队手里在做的一个项目,其中一个小功能是用户需要上传PDF文件到文件服务器上,都是一些合同或者技术评估文档,鉴于知识版权和防伪的目的,需要在上传的PDF文件打上水印, 这时候我们需要提供能力给客 ...

  8. mapstruct 快速使用

    mapstruct 快速使用 mapstruct 主要的作用则是用来复制对象字段使用,功能非常的强大.在没有使用 mapstruct 之前可能都在使用 BeanUtils ,但是 BeanUtils ...

  9. Alibaba Spring Cloud 微服务介绍(一)

    " Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案.此项目包含开发分布式应用服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件 ...

  10. JVM详解(二)-- 第2章 类加载器子系统

    一.JVM内存结构 1.1 内存结构---概略图 1.2 内存结构--详细图 二.类加载器子系统的作用 类加载器子系统负责从文件系统或网络中加载.Class文件,文件需要有特定的标识(cafe bab ...