目录
dubbo springboot启动方式,自动装配bean DubboAutoConfiguration,创建bean ServiceAnnotationBeanPostProcessor,是个BeanDefinitionRegistryPostProcessor。因此在com.alibaba.dubbo.config.spring.beans.factory.annotation.ServiceAnnotationBeanPostProcessor.postProcessBeanDefinitionRegistry(BeanDefinitionRegistry)
方法内,扫描@com.alibaba.dubbo.config.annotation.Service注解,注册为IOC bean,同时注册IOC内一个 ServiceBean,其ref就是IOC bean。
比如下面定义一个dubbo provider
@com.alibaba.dubbo.config.annotation.Service
public class ProductServiceImpl implements ProductService {
//内容略
}
复制
在IOC 内注册两个bean,一个是beanName=productServiceImpl ,bean对象就是ProductServiceImpl;另外一个是ServiceBean,其ref就是ProductServiceImpl。
PS:值得参考的是ServiceAnnotationBeanPostProcessor对于@com.alibaba.dubbo.config.annotation.Service注解扫描注解为bean的用法挺好,以后我们自定义注解扫描,也可以直接参考拿来使用。
ServiceBean是个InitializingBean、Aware、ApplicationListener,因此在IOC bean的初始化过程也会执行InitializingBean、Aware、ApplicationListener。
ServiceBean采用了模板,ServiceBean持有ProviderConfig、ApplicationConfig、ModuleConfig、RegistryConfig、MonitorConfig、ProtocolConfig
在afterPropertiesSet()内就是设置ProviderConfig、ApplicationConfig、ModuleConfig、RegistryConfig、MonitorConfig、ProtocolConfig保存到ServiceBean。 提问:这些XXXConfig是怎么来的呢?后面解释
接着判断非延时暴露,则直接暴露服务,否则在监听器执行时候进行暴露。
检查属性or环境变量的dubbo.xxx.配置开头的属性,配置到XXXConfig对象,本质就是保存到ServiceBean对象。
检测dubbo.provider.开头属性,配置到ProviderConfig
检测dubbo.application.开头属性,配置到ApplicationConfig
检测dubbo.registry.开头属性,配置到RegistryConfig
检测dubbo.service.开头属性,配置到ServiceConfig,即ServiceBean
遍历注册中心,把服务注册到每个注册中心。
本地暴露,感觉鸡肋,本来是个IOC bean了,在当前服务内直接使用就行了,本地暴露忽略。
远程暴露分两步
step1: 使用ProxyFactory对目标ref生成代理对象Invoker。
step2: 把step1生成的Invoker对象暴露到zk注册中心。
使用ProxyFactory对目标对象ref生成代理对象Invoker。默认使用的是JavassistProxyFactory.getInvoker()
此时协议是registry,使用的是RegistryProtocol进行暴露服务
step1: 使用wrapper类,对Invoker增强,创建filter chain
step2: 使用DubboProtocol进行暴露服务,即封装invoker为DubboExporter,并缓存到DubboProtocol.exporterMap,这个map就是用于接收consumer请求时,根据port+interfaceName+version+group作为key,从此缓存map中获取到DubboExporter。
step3: 创建tcp server,监听20880端口(默认)
核心代码如下
//com.alibaba.dubbo.registry.integration.RegistryProtocol.doLocalExport(Invoker<T>)
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker) {
String key = getCacheKey(originInvoker);
ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
if (exporter == null) {
synchronized (bounds) {
exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
if (exporter == null) {
final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);//DubboProtocol.export
bounds.put(key, exporter);
}
}
}
return exporter;
}
复制
step1: 连接zk,与zk长连接
step2: 在zk创建provider服务临时节点
step3: 创建/dubbo/$interfaceName/configurators
节点,并订阅此节点,如果此节点有变化,则dubbo provider服务重新暴露(OverrideListener)。这个对应dubbo-admin上的动态配置。
核心代码如下在ZookeeperRegistry.doSubscribe(URL, NotifyListener)
dubbo provider启动流程总结,根据官方启动流程图如下
服务提供者暴露服务的主过程:
首先 ServiceConfig 类拿到对外提供服务的实际类 ref(如:HelloWorldImpl),然后通过 ProxyFactory 类的 getInvoker 方法使用 ref 生成一个 AbstractProxyInvoker 实例,到这一步就完成具体服务到 Invoker 的转化。接下来就是 Invoker 转换到 Exporter 的过程。
Dubbo 处理服务暴露的关键就在 Invoker 转换到 Exporter 的过程,上图中的红色部分
Dubbo 协议的 Invoker 转为 Exporter 发生在 DubboProtocol 类的 export 方法,它主要是打开 socket 侦听服务,并接收客户端发来的各种请求,通讯细节由 Dubbo 自己实现。
zk协议是把Exporter 注册到注册中心,并监听注册中心对应节点的变化。
dubbo provider启动流程
provider启动记录xmind地址https://gitee.com/yulewo123/mdpicture/blob/master/document/dubbo/dubbo provider启动记录.xmind
dubbo 服务泛化不能采用注解方式,只能使用xml或java api方式。以java api方式为例
//定义一个service
public class MyGenericService implements GenericService {//必须实现GenericService
public Object $invoke(String methodName, String[] parameterTypes, Object[] args) throws GenericException {
if ("sayHello".equals(methodName)) {
return "Welcome " + args[0];
}
}
}
//
@org.springframework.stereotype.Service
public class DubboGenericService implements SmartInitializingSingleton {
@Override
public void afterSingletonsInstantiated() {
ServiceConfig<GenericService> service = new ServiceConfig<GenericService>();
ApplicationConfig application = new ApplicationConfig("test-provider");
service.setApplication(application);
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
service.setRegistry(registryConfig);
service.setInterface("com.suibian.WhateverService");//用于指定要发布的接口名称,这个名称可以不对应任何的java接口类,甚至可以随便写。
GenericService genericService = (method, parameterTypes, args) -> {
if ("method1".equals(method)) {
return "method1 result:" + args[0];
}
if ("method2".equals(method)) {
return 12345;
}
if (parameterTypes.length == 2
&& parameterTypes[0].equals("java.lang.String")
&& parameterTypes[1].equals("java.lang.Integer")
&& "method3".equals(method)) {
return "method3,param1:" + args[0] + ",param2:" + args[1];
}
return null;
};
service.setRef(genericService);
service.export();
}
}
复制
ServiceConfig.setInterface()用于指定要发布的接口名称,这个名称可以不对应任何的java接口类,甚至可以随便写。在上面的例子中com.suibian.WhateverService这个名字就是随便写的,实际上代码中并没有这个接口类。
因为这样发布的Dubbo服务没有具体的接口类,所以invoke()方法的第一个参数String var1(原本是方法名)和第二个参数String[] var2(原本是方法参数类型列表)就脱离了具体接口类的束缚,可以接收任意值,就像例子中所写的一样,按照不同的参数执行不同的逻辑,就像一个网关一样(一个接口行天下)。
因此当服务提供者使用GenericService接口时,可以省略Interface的代码,省略方法和参数的声明,通过指定接口名称的方式向zookeeper发布Dubbo服务。这时GenericService就像是一个网关。
泛化服务启动和普通服务启动流程完全一致,ref通过ProxyFactory生成Invoker,Invoker通过DubboProtocol进行服务暴露,通过RegistryProtocol注册到zk。 dubbo泛化服务注册到zk例子 dubbo://192.168.5.1:20880/com.suibian.WhateverService?anyhost=true&application=test-provider&dubbo=2.0.2&generic=true&interface=com.suibian.WhateverService&methods=*&pid=12744&side=provider×tamp=1625669557658
和普通服务细节不同之处是:
1.ServiceConfig.interfaceClass是GenericService,ServiceConfig.interfaceName是自定义的服务名称
2.会在filter chain增加个GenericImplFilter,用于解析泛化请求
@Reference注解由ReferenceBean处理,实现了Aware、InitializingBean、FactoryBean。因此
首先,在afterPropertiesSet()操作内保存ConsumerConfig、ApplicationConfig、ModuleConfig、RegistryConfig、MonitorConfig到ReferenceBean。
然后,由于是FactoryBean,因此在getObject()获取真实的对象。
consumer生成核心在createProxy,作用是生成代理。本质是由DubboProtocol生成DubboInvoker,DubboInvoker封装了引用的接口和nettyClient。然后把生成的Invoker缓存到注册表RegistryDirectory.methodInvokerMap。这样在consumer请求provider时候,就可以根据methodName从缓存中获取Invoker。生成Invoker后,订阅providers、configurators、routers节点,这些节点有变动,则会通过zk通知到consumer端进行Invoker重新生成。
consumer启动的核心在RegistryDirectory。
启动流程图如下
consumer 启动记录xmind地址https://gitee.com/yulewo123/mdpicture/blob/master/document/dubbo/dubbo consumer启动记录.xmind
dubbo泛化调用通常用于网关,例子如下
ReferenceConfigCache referenceCache = ReferenceConfigCache.getCache();
ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();//缓存,否则每次请求都会创建一个ReferenceConfig,并在zk注册节点,最终可能导致zk节点过多影响性能
ApplicationConfig application = new ApplicationConfig();
application.setName("pangu-client-consumer-generic");
// 连接注册中心配置
RegistryConfig registry = new RegistryConfig();
registry.setAddress("zookeeper://127.0.0.1:2181");
// 服务消费者缺省值配置
ConsumerConfig consumer = new ConsumerConfig();
consumer.setTimeout(10000);
consumer.setRetries(0);
reference.setApplication(application);
reference.setRegistry(registry);
reference.setConsumer(consumer);
reference.setInterface(org.pangu.api.ProductService.class); // 弱类型接口名
// reference.setVersion("");
// reference.setGroup("");
reference.setGeneric(true); // 声明为泛化接口
GenericService svc = referenceCache.get(reference);
Object target = svc.$invoke("findProduct", new String[]{ProductDTO.class.getName()}, new Object[]{dto});//实际网关中,方法名、参数类型、参数是作为参数传入
复制
consumer泛化,无法以注解方式启动,通常都是使用java api方式启动,通常都是在请求时候进行创建并缓存。
consumer是泛化,启动流程和普通consumer基本相同,不同之处有两点:
备注:
普通consumer在zk注册节点例子:consumer://192.168.5.1/org.pangu.api.ProductService?application=pangu-client-consumer&category=consumers&check=false&dubbo=2.0.2&interface=org.pangu.api.ProductService&methods=findProduct,selectProduct&pid=19276&qos.enable=true&retries=0&revision=1.0-SNAPSHOT&side=consumer&timeout=900000000×tamp=1625758276910
泛化consumer在zk注册节点例子:consumer://192.168.5.1/com.alibaba.dubbo.rpc.service.GenericService?application=pangu-client-consumer-generic&category=consumers&check=false&default.retries=0&default.timeout=60000&dubbo=2.0.2&generic=true&interface=org.pangu.api.ProductService&pid=19276&side=consumer×tamp=1625758288017
通过分析dubbo启动,发现provider和consumer都是转成Invoker。
provider端首先通过javaassit对目标接口进行动态代理,生成代理对象Invoker,然后对此Invoker进行Decorator增强(封装目标服务和nettyserver),接着再继续Decorator增强(生成filter chain),得到最终增强后的Invoker对象,并缓存到DubboProtocol.exporterMap。
consumer端是直接创建DubboInvoker,该Invoker封装了目标服务类型clazz和nettyClient,接着也对此Invoker进行Decorator增强(生成filter chain),得到最终增强后的Invoker对象,并缓存到RegistryDirectory.methodInvokerMap。
使用Invoker,统一不同的接口类型,便于管理以及重要的是可以对一个类型设置拦截,用于功能增强。这也是梁飞大神提到的在重要的过程上设置拦截接口
手机扫一扫
移动阅读更方便
你可能感兴趣的文章