一、服务发布的过程
- dubbo要想发布一个服务需要做到以下几点
1、解析配置文件 2、通过netty去暴露一个服务 3、序列化和反序列化 4、将信息发布到zk上
- dubbo依赖spring的SPI进行服务发布,通过继承spring提供的NamespaceHandlerSupport加载application.xml的配置信息,并通过继承spring提供的BeanDefinitionParser解析xml。
public class DubboNamespaceHandler extends NamespaceHandlerSupport { static { Version.checkDuplicate(DubboNamespaceHandler.class); } @Override public void init() { registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true)); registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true)); registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true)); registerBeanDefinitionParser("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true)); registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true)); registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true)); registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.class, true)); registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true)); registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true)); registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true)); registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true)); registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false)); registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser()); } }
- 如果是springboot,则通过springboot的import方式解析
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @EnableDubboConfig @DubboComponentScan public @interface EnableDubbo { //省略 } @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @Import(DubboConfigConfigurationRegistrar.class) public @interface EnableDubboConfig { /** * It indicates whether binding to multiple Spring Beans. * * @return the default value is <code>false</code> * @revised 2.5.9 */ boolean multiple() default true; } //DubboConfigConfigurationRegistrar,DubboComponentScanRegistrar ,ServiceAnnotationBeanPostProcessor,ReferenceAnnotationBeanPostProcessor
二、ServiceBean
-
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware, ApplicationEventPublisherAware { //省略 }
实现Spring提供的接口
InitializingBean :afterPropertiesSet() 配置的初始化
DisposableBean:destroy()
ApplicationContextAware:setApplicationContext(ApplicationContext application)
ApplicationListener:onApplicationEvent(ContextRefreshedEvent event) spring初始化后 发布服务
BeanNameAware:setBeanName(String beanName)
ApplicationEventPublisherAware: setApplicationEventPublisher(ApplicationEventPublisher publisher) -
重点看ServiceBean的onApplicationEvent方法
1.spring容器刷新时回调事件 @Override public void onApplicationEvent(ContextRefreshedEvent event) { if (!isExported() && !isUnexported()) { if (logger.isInfoEnabled()) { logger.info("The service ready on spring started. service: " + getInterface()); } //导出、发布服务 export(); } } @Override public void export() { super.export(); // Publish ServiceBeanExportedEvent publishExportEvent(); }
2. ServiceConfig public synchronized void export() { //检查.更新配置 checkAndUpdateSubConfigs(); //当前服务是否要发布,xml或者注解配置 if (!shouldExport()) { return; } //是否延迟发布 if (shouldDelay()) { delayExportExecutor.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS); } else { doExport(); } } 3. protected synchronized void doExport() { if (unexported) { throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!"); } if (exported) { return; } exported = true; if (StringUtils.isEmpty(path)) { path = interfaceName; } doExportUrls(); } 4. private void doExportUrls() { //加载注册中心,并且声称URL地址 //配置信息转换成URL,dubbo基于url驱动, (registry://122.51.167.43:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-service&check=true&dubbo=2.0.2&group=dubbo-test&owner=neo&pid=3660&qos.enable=false®istry=zookeeper&release=2.7.1&simplified=true×tamp=1584370878274) List<URL> registryURLs = loadRegistries(true); //dubbo协议 for (ProtocolConfig protocolConfig : protocols) { //接口名 , group和version组成pathKey String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version); //存储服务发布的元数据 ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass); ApplicationModel.initProviderModel(pathKey, providerModel); doExportUrlsFor1Protocol(protocolConfig, registryURLs); } } 5. private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) { //拿到协议名name =dubbo String name = protocolConfig.getName(); if (StringUtils.isEmpty(name)) { name = DUBBO; } Map<String, String> map = new HashMap<String, String>(); map.put(SIDE_KEY, PROVIDER_SIDE); appendRuntimeParameters(map); appendParameters(map, metrics); appendParameters(map, application); appendParameters(map, module); // remove 'default.' prefix for configs from ProviderConfig // appendParameters(map, provider, Constants.DEFAULT_KEY); appendParameters(map, provider); appendParameters(map, protocolConfig); appendParameters(map, this); //对method标签进行解析 /*<dubbo:service> <dubbo:method> </dubbo:method> </dubbo:service> */ if (CollectionUtils.isNotEmpty(methods)) { for (MethodConfig method : methods) { appendParameters(map, method, method.getName()); String retryKey = method.getName() + ".retry"; if (map.containsKey(retryKey)) { String retryValue = map.remove(retryKey); if ("false".equals(retryValue)) { map.put(method.getName() + ".retries", "0"); } } List<ArgumentConfig> arguments = method.getArguments(); if (CollectionUtils.isNotEmpty(arguments)) { for (ArgumentConfig argument : arguments) { // convert argument type if (argument.getType() != null && argument.getType().length() > 0) { Method[] methods = interfaceClass.getMethods(); // visit all methods if (methods != null && methods.length > 0) { for (int i = 0; i < methods.length; i++) { String methodName = methods[i].getName(); // target the method, and get its signature if (methodName.equals(method.getName())) { Class<?>[] argtypes = methods[i].getParameterTypes(); // one callback in the method if (argument.getIndex() != -1) { if (argtypes[argument.getIndex()].getName().equals(argument.getType())) { appendParameters(map, argument, method.getName() + "." + argument.getIndex()); } else { throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType()); } } else { // multiple callbacks in the method for (int j = 0; j < argtypes.length; j++) { Class<?> argclazz = argtypes[j]; if (argclazz.getName().equals(argument.getType())) { appendParameters(map, argument, method.getName() + "." + j); if (argument.getIndex() != -1 && argument.getIndex() != j) { throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType()); } } } } } } } } else if (argument.getIndex() != -1) { appendParameters(map, argument, method.getName() + "." + argument.getIndex()); } else { throw new IllegalArgumentException("Argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>"); } } } } // end of methods for } if (ProtocolUtils.isGeneric(generic)) { map.put(GENERIC_KEY, generic); map.put(METHODS_KEY, ANY_VALUE); } else { String revision = Version.getVersion(interfaceClass, version); if (revision != null && revision.length() > 0) { map.put(REVISION_KEY, revision); } String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames(); if (methods.length == 0) { logger.warn("No method found in service interface " + interfaceClass.getName()); map.put(METHODS_KEY, ANY_VALUE); } else { map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ",")); } } if (!ConfigUtils.isEmpty(token)) { if (ConfigUtils.isDefault(token)) { map.put(TOKEN_KEY, UUID.randomUUID().toString()); } else { map.put(TOKEN_KEY, token); } } // export service //主机绑定 String host = this.findConfigedHosts(protocolConfig, registryURLs, map); Integer port = this.findConfigedPorts(protocolConfig, name, map); //url = dubbo://192.168.56.1:20881/com.zookeeper.curator.dubbo.ISayHelloService?anyhost=true&application=dubbo-service&bean.name=providers:dubbo:com.zookeeper.curator.dubbo.ISayHelloService&bind.ip=192.168.56.1&bind.port=20881&default.deprecated=false&default.dynamic=false&default.register=true&deprecated=false&dubbo=2.0.2&dynamic=false&generic=false&interface=com.zookeeper.curator.dubbo.ISayHelloService&methods=sayHello&owner=neo&pid=3660&qos.enable=false®ister=true&release=2.7.1&side=provider×tamp=1584371830033 URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map); //TODO 动态配置修改 if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class) .hasExtension(url.getProtocol())) { url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class) .getExtension(url.getProtocol()).getConfigurator(url).configure(url); } //scope 选择服务发布的范围(local/remote) //local=同一个jvm里面调用 ; //remote : dubbo://ip:port //默认情况下,如果是配置remote(registry),默认发布远程和本地 String scope = url.getParameter(SCOPE_KEY); // don't export when none is configured if (!SCOPE_NONE.equalsIgnoreCase(scope)) { // 如果是本地发布,则直接调用exportLocal if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) { exportLocal(url); //TODO } // export to remote if the config is not local (export to local only when config is local) if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) { if (!isOnlyInJvm() && logger.isInfoEnabled()) { logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url); } if (CollectionUtils.isNotEmpty(registryURLs)) { for (URL registryURL : registryURLs) { //registryURL: registry://ip:port... //if protocol is only injvm ,not register if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) { continue; } url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY)); URL monitorUrl = loadMonitor(registryURL); if (monitorUrl != null) { url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString()); } if (logger.isInfoEnabled()) { logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL); } // For providers, this is used to enable custom proxy to generate invoker String proxy = url.getParameter(PROXY_KEY); if (StringUtils.isNotEmpty(proxy)) { registryURL = registryURL.addParameter(PROXY_KEY, proxy); } //TODO invoker -> 代理类 Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString())); //MetaData元数据的委托 DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); //wrapperInvoker : registry:/// //protocol是一个自适应扩展点, @Adaptive在方法上,会生成这个类 → Protocol$Adaptive //Protocol$Adaptive的export方法 会从url拿到name=registry的静态扩展点 → RegistryProtocol //6. Exporter<?> exporter = protocol.export(wrapperInvoker); exporters.add(exporter); } } else { Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url); DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); Exporter<?> exporter = protocol.export(wrapperInvoker); exporters.add(exporter); } /** * @since 2.7.0 * ServiceData Store */ MetadataReportService metadataReportService = null; if ((metadataReportService = getMetadataReportService()) != null) { metadataReportService.publishProvider(url); } } } this.urls.add(url); }
6.RegistryProtocol的export方法 @Override public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException { // registryUrl -> 由 registry://ip:prot 换成 zookeeper://ip:port URL registryUrl = getRegistryUrl(originInvoker); // providerUrl -> 拿到dubbo:// ip:port URL providerUrl = getProviderUrl(originInvoker); // Subscribe the override data // FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call // the same service. Because the subscribed is cached key with the name of the service, it causes the // subscription information to cover. final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl); final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker); overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener); providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener); /***********************************/ //doLocalExport 本质就是去启动一个netty服务 //7. final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl); // 把dubbo:// url注册到zk上 final Registry registry = getRegistry(originInvoker); final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl); ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl); //to judge if we need to delay publish boolean register = registeredProviderUrl.getParameter("register", true); if (register) { register(registryUrl, registeredProviderUrl); providerInvokerWrapper.setReg(true); } // Deprecated! Subscribe to override rules in 2.6.x or before. registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener); exporter.setRegisterUrl(registeredProviderUrl); exporter.setSubscribeUrl(overrideSubscribeUrl); //Ensure that a new exporter instance is returned every time export return new DestroyableExporter<>(exporter); }
7. private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) { String key = getCacheKey(originInvoker); //bounds -chm ->computeIfAbsent if(map.get(key)==null){map.put()} return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> { //orginInvoker-> InvokerDelegate(DelegateProviderMetaDataInvoker(invoker)) Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl); //protocol.export -> 因为这个protocol自动注入的 所以这个protocol=Protocol$Apaptive //protocol- >Protocol$Apaptive ->QosProtocolWrapper(ProtocolListenerWrapper(ProtocolFilterWrapper(DubboProtocol(invoker)))) //8. return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker); }); }
8.protocol.export(invokerDelegate)这个protocol比较有意思,实际上是dubbo将DubboProtocl这个类层层包装后的, protocol = new QosProtocolWrapper(new ProtocolListenerWrapper(new ProtocolFilterWrapper(new DubboProtocol(invokerDelegate)))); 原因如下在ExtensionLoader的loadClass方法: private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException { //省略代码 /* public ProtocolFilterWrapper(Protocol protocol) { if (protocol == null) { throw new IllegalArgumentException("protocol == null"); } this.protocol = protocol; }*/ //会判断有没有这个带扩展点的构造器,并缓存到cachedWrapperClasses集合中 } else if (isWrapperClass(clazz)) { cacheWrapperClass(clazz); } } 等加载完了扩展点后,开始包装: private T createExtension(String name) { //加载扩展点 Class<?> clazz = getExtensionClasses().get(name); if (clazz == null) { throw findException(name); } try { T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null) { EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } //自动注入扩展点 injectExtension(instance); //这里就是包装的地方,顺带自动注入 Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (CollectionUtils.isNotEmpty(wrapperClasses)) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; } catch (Throwable t) { throw new IllegalStateException("Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: " + t.getMessage(), t); } } 看这些包装类就能猜到dubbo这样做的目的就是功能增强。
9. 最终调用DubboProtocol的export方法 @Override public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { URL url = invoker.getUrl(); // export service. String key = serviceKey(url); DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap); exporterMap.put(key, exporter); //export an stub service for dispatching event Boolean isStubSupportEvent = url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT); Boolean isCallbackservice = url.getParameter(IS_CALLBACK_SERVICE, false); if (isStubSupportEvent && !isCallbackservice) { String stubServiceMethods = url.getParameter(STUB_EVENT_METHODS_KEY); if (stubServiceMethods == null || stubServiceMethods.length() == 0) { if (logger.isWarnEnabled()) { logger.warn(new IllegalStateException("consumer [" + url.getParameter(INTERFACE_KEY) + "], has set stubproxy support event ,but no stub methods founded.")); } } else { stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods); } } //用什么方式发布一个服务,netty等等 openServer(url); //优化序列化 optimizeSerialization(url); return exporter; }