三、远程暴露
1. 配置
远程暴露的配置其实与本地暴露雷同,只需将 scope 的值赋成 remote 即可,并指定使用的协议
<bean id="demoServiceTarget" class="org.apache.dubbo.samples.local.impl.DemoServiceImpl"/> <!-- 服务提供者指定scope --> <dubbo:service interface="org.apache.dubbo.samples.local.api.DemoService" ref="demoServiceTarget" scope="remote"/> <dubbo:protocol name="dubbo" port="20880" /> <dubbo:reference id="demoService" interface="org.apache.dubbo.samples.local.api.DemoService"/>
2. 实现原理
同本地暴露一样,我们仍然来看基础方法 ServiceConfig 下私有方法 exportUrl
private void exportUrl(URL url, List<URL> registryURLs) { String scope = url.getParameter(SCOPE_KEY); if (!SCOPE_NONE.equalsIgnoreCase(scope)) { if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) { // 本地暴露略 } // 远程暴露 if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) { // 远程暴露获取额外设置的协议 String extProtocol = url.getParameter("ext.protocol", ""); List<String> protocols = new ArrayList<>(); if (StringUtils.isNotBlank(extProtocol)) { // export original url url = URLBuilder.from(url). addParameter(IS_PU_SERVER_KEY, Boolean.TRUE.toString()). removeParameter("ext.protocol"). build(); } // 远程暴露 url = exportRemote(url, registryURLs); if (!isGeneric(generic) && !getScopeModel().isInternal()) { MetadataUtils.publishServiceDefinition(url, providerModel.getServiceModel(), getApplicationModel()); } if (StringUtils.isNotBlank(extProtocol)) { String[] extProtocols = extProtocol.split(",", -1); protocols.addAll(Arrays.asList(extProtocols)); } // 对于额外设定的协议,也进行暴露 for(String protocol : protocols) { if(StringUtils.isNotBlank(protocol)){ URL localUrl = URLBuilder.from(url). setProtocol(protocol). build(); localUrl = exportRemote(localUrl, registryURLs); if (!isGeneric(generic) && !getScopeModel().isInternal()) { MetadataUtils.publishServiceDefinition(localUrl, providerModel.getServiceModel(), getApplicationModel()); } this.urls.add(localUrl); } } } } this.urls.add(url); }
通过上述方法,我们可以知道,在服务远程暴露的时候,可以设置额外的导出协议,这将导致一个服务将以多个协议的形式进行暴露,但不管是哪种方式,其核心方法都是调用的 exportRemote,该方法第一个参数为要暴露的服务的信息,第二个参数为各注册中心的信息,我们来看看该方法。
private URL exportRemote(URL url, List<URL> registryURLs) { if (CollectionUtils.isNotEmpty(registryURLs)) { // 遍历各注册中心 for (URL registryURL : registryURLs) { // 判断是否是service-discovery-registry协议,该协议用于自省架构 if (SERVICE_REGISTRY_PROTOCOL.equals(registryURL.getProtocol())) { url = url.addParameterIfAbsent(SERVICE_NAME_MAPPING_KEY, "true"); } // 本地暴露不予执行 if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) { continue; } url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY)); URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL); if (monitorUrl != null) { url = url.putAttribute(MONITOR_KEY, monitorUrl); } // 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); } if (logger.isInfoEnabled()) { if (url.getParameter(REGISTER_KEY, true)) { logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL.getAddress()); } else { logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url); } } // 把服务消息作为一个属性包裹在注册中心url内,并执行导出 doExportUrl(registryURL.putAttribute(EXPORT_KEY, url), true); } } else { if (logger.isInfoEnabled()) { logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url); } doExportUrl(url, true); } return url; }
可以看到,如果指定了注册中心,会把服务消息包装成在各个注册中心的URL中,我们以ZK为例,当我们向Zookeeper 进行注册时,会获得如下的注册Url
并进行发布;如果没有,则执行默认的发布,方法都是 doExportUrl,这个我们在本地暴露时已经讲过,会根据协议类型来选择实现类来进行发布,此处显然就是 RegistryProtocol 作为实现类,我们直接找到其注册的位置
// RegistryProtocol private void register(Registry registry, URL registeredProviderUrl) { registry.register(registeredProviderUrl); }
最后则是以 ZookeeperRegistry 通过ZK客户端向Zookeeper 注册服务信息
// ZookeeperRegistry public void doRegister(URL url) { try { checkDestroyed(); zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true), false); } catch (Throwable e) { throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e); } }
四、服务的注解配置
除了上面通过xml文件进行的配置,Dubbo其实更适合使用注解进行服务的启用,当然由于历代Dubbo的变化,注解也发生了变化
org.apache.dubbo.config.annotation.DubboService (Since:2.7.7)
org.apache.dubbo.config.annotation.Service (Since:2.7.0)
com.alibaba.dubbo.config.annotation.Service (阿里时期)
我们以最新的 @DubboService 来看看,其中很多字段都是与XML的配置完全一样的
当然,我们上面提到的 scope 也在其中,用法也是完全相同
五、服务暴露的触发
我们前面说的服务暴露,都是以 ServiceConfig 下私有方法 exportUrl 为起点,但是当应用启动时,这个方法是怎样被触发的呢,我们现在来看这个问题。我们以结合Spring 的项目来进行研究
1. dubbo 的启动
我们在前面进行工程搭建的时候,就提到部署时需要在启动类上写上注解 @EnableDubbo,大部分三方框架都需要以类似的方式执行与Spring工程的融合,我们先来看看加上该注解的原理吧。
这两个注解顾名思义,一个负责启用默认配置,一个负责加载组件。我们现在看组件部分
关于注解上再使用 @Import 注解,其实也是诸多框架里经常采用的导入Spring的模式了,该注解能用我们在外层注解上的值进行某些操作,在此处,就是把该目录下的组件扫描进Spring容器中
2. 服务识别——后置处理器
经过上述的组件扫描,我们要获得什么呢?当然是我们配置的服务或者引用能够正确的装载进Spring容器了
// DubboComponentScanRegistrar private void registerServiceAnnotationPostProcessor(Set<String> packagesToScan, BeanDefinitionRegistry registry) { // 获取 ServiceAnnotationPostProcessor 的信息,将其的Bean定义注入Spring容器 BeanDefinitionBuilder builder = rootBeanDefinition(ServiceAnnotationPostProcessor.class); builder.addConstructorArgValue(packagesToScan); builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); AbstractBeanDefinition beanDefinition = builder.getBeanDefinition(); BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry); } public static String registerWithGeneratedName(AbstractBeanDefinition definition, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { String generatedName = generateBeanName(definition, registry, false); // 注入进Spring 容器 registry.registerBeanDefinition(generatedName, definition); return generatedName; }
这里,我们需要知道,把ServiceAnnotationPostProcessor的Bean定义注入进Spring的原因是什么,因为其是一个后置处理器,它继承了BeanDefinitionRegistryPostProcessor,能负责在Bean工厂创建完成后执行操作,我们在MyBatis+Springboot 启动到SQL执行全流程 和 pringBean生成流程详解 两篇文章中都有解释,此处就不再赘述。来关注下它做了什么。
// 至今为止,Dubbo 经历过几个阶段,使用的服务注解各不相同,此处算做个兼容 private final static List<Class<? extends Annotation>> serviceAnnotationTypes = asList( // @since 2.7.7 Add the @DubboService , the issue : https://github.com/apache/dubbo/issues/6007 DubboService.class, // @since 2.7.0 the substitute @com.alibaba.dubbo.config.annotation.Service Service.class, // @since 2.7.3 Add the compatibility for legacy Dubbo's @Service , the issue : https://github.com/apache/dubbo/issues/4330 com.alibaba.dubbo.config.annotation.Service.class ); public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { if (this.registry == null) { // In spring 3.x, may be not call postProcessBeanDefinitionRegistry() this.registry = (BeanDefinitionRegistry) beanFactory; } // 扫描Bean定义 String[] beanNames = beanFactory.getBeanDefinitionNames(); for (String beanName : beanNames) { BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); Map<String, Object> annotationAttributes = getServiceAnnotationAttributes(beanDefinition); if (annotationAttributes != null) { // process @DubboService at java-config @bean method processAnnotatedBeanDefinition(beanName, (AnnotatedBeanDefinition) beanDefinition, annotationAttributes); } } if (!scanned) { // 把目录下符合条件的类定义,注册进Spring容器 scanServiceBeans(resolvedPackagesToScan, registry); } }
3. 服务暴露的时机
首先我们得知道Spring 提供的监听器机制(不了解的可以看这篇:Spring监听器用法与原理详解),知道了在Spring容器刷新完毕后的时候会发送事件 ContextRefreshedEvent
// AbstractApplicationContext.class protected void finishRefresh() { this.clearResourceCaches(); this.initLifecycleProcessor(); this.getLifecycleProcessor().onRefresh(); this.publishEvent((ApplicationEvent)(new ContextRefreshedEvent(this))); if (!NativeDetector.inNativeImage()) { LiveBeansView.registerApplicationContext(this); } }
而Dubbo 正是利用了这个机制来进行启动的,Dubbo内建了一个监听器 DubboDeployApplicationListener 来监听 ContextRefreshedEvent 事件,以此为触发,进行Dubbo模块的启动
// DubboDeployApplicationListener.class private void onContextRefreshedEvent(ContextRefreshedEvent event) { ModuleDeployer deployer = moduleModel.getDeployer(); Assert.notNull(deployer, "Module deployer is null"); // 启动模块 Future future = deployer.start(); // if the module does not start in background, await finish if (!deployer.isBackground()) { try { future.get(); } catch (InterruptedException e) { logger.warn(CONFIG_FAILED_START_MODEL, "", "", "Interrupted while waiting for dubbo module start: " + e.getMessage()); } catch (Exception e) { logger.warn(CONFIG_FAILED_START_MODEL, "", "", "An error occurred while waiting for dubbo module start: " + e.getMessage(), e); } } }
但是,我们还有一个问题,就是我们知道Spring的监听器必须置于Spring容器下,如果是我们自己写代码,类上加个注解@Component就可以解决,但眼下情形显然不同,==这个监听器是如何融入Spring体系并将自身加入进Spring容器的呢?==其实上面就已经提到了
这里的 DubboSpringInitializer.initialize(registry) 就是把Dubbo 自身的很多内容注册进Spring 容器的,其调用链路为
initialize -> initContext -> DubboBeanUtils.registerCommonBeans(registry)
static void registerCommonBeans(BeanDefinitionRegistry registry) { registerInfrastructureBean(registry, ServicePackagesHolder.BEAN_NAME, ServicePackagesHolder.class); registerInfrastructureBean(registry, ReferenceBeanManager.BEAN_NAME, ReferenceBeanManager.class); // Since 2.5.7 Register @Reference Annotation Bean Processor as an infrastructure Bean registerInfrastructureBean(registry, ReferenceAnnotationBeanPostProcessor.BEAN_NAME, ReferenceAnnotationBeanPostProcessor.class); // TODO Whether DubboConfigAliasPostProcessor can be removed ? // Since 2.7.4 [Feature] https://github.com/apache/dubbo/issues/5093 registerInfrastructureBean(registry, DubboConfigAliasPostProcessor.BEAN_NAME, DubboConfigAliasPostProcessor.class); // 注册监听器,其中就包含了我们这次提到的 DubboDeployApplicationListener registerInfrastructureBean(registry, DubboDeployApplicationListener.class.getName(), DubboDeployApplicationListener.class); registerInfrastructureBean(registry, DubboConfigApplicationListener.class.getName(), DubboConfigApplicationListener.class); // Since 2.7.6 Register DubboConfigDefaultPropertyValueBeanPostProcessor as an infrastructure Bean registerInfrastructureBean(registry, DubboConfigDefaultPropertyValueBeanPostProcessor.BEAN_NAME, DubboConfigDefaultPropertyValueBeanPostProcessor.class); // Dubbo config initializer registerInfrastructureBean(registry, DubboConfigBeanInitializer.BEAN_NAME, DubboConfigBeanInitializer.class); // register infra bean if not exists later registerInfrastructureBean(registry, DubboInfraBeanRegisterPostProcessor.BEAN_NAME, DubboInfraBeanRegisterPostProcessor.class); }
4. 与Spring结合的流程图