如果你看到了这里,那么接下来你将会认识Dubbo3的诞生将如何引领微服务领域更进一步,从而迈入云原生的领域,这当然不仅仅是Dubbo3,之前也介绍了Java生态另外一个云原生领域的技术Quarkus等技术,而本文内容侧重点去介绍Dubbo3迈向云原生
的技术分析和探索,如果有不正确的地方,还需要大家多多指正。
Dubbo3的泛化技术而言主要是面向于外部接口调用抽象能力和抽象粒度的泛化提升,主要对于服务的提供者和消费组均有泛化实现的体系功能。
Dubbo3服务端泛接口实现方式主要用于服务器端没有API接口及模型类元的情况,参数及返回值中的所有POJO均用Map表示,通常用于框架集成,比如:实现一个通用的远程服务框架,可通过实现RawTypeService接口处理所有服务请求。
我们自己定义一个接口实现类机制命名为RawTypeService类,用于继承我们Dubbo框架默认的泛型标准接口:org.apache.dubbo.rpc.service.GenericService,GenericService的源码如下。
package org.apache.dubbo.rpc.service;
import java.util.concurrent.CompletableFuture;
/**
* Generic service interface
* @export
*/
public interface GenericService {
/**
* Generic invocation
*
* @param method Method name, e.g. findPerson. If there are overridden methods, parameter info is
* required, e.g. findPerson(java.lang.String)
* @param parameterTypes Parameter types
* @param args Arguments
* @return invocation return value
* @throws GenericException potential exception thrown from the invocation
*/
Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException;
default CompletableFuture<Object> $invokeAsync(String method, String[] parameterTypes, Object[] args) throws GenericException {
Object object = $invoke(method, parameterTypes, args);
if (object instanceof CompletableFuture) {
return (CompletableFuture<Object>) object;
}
return CompletableFuture.completedFuture(object);
}
}
GenericService这个业务逻辑接口类是框架提供给我们的实现泛型话功能的调用机制体系,那么现在就来试一下如何定义一下我们自己的泛型RPC调用框架服务。
用于暴漏给客户端进行暴漏调用使用的接口服务实现类机制。
public interface GenericTypeService {
String process(String name);
String execute(String name);
}
在Java代码中实现RawTypeService接口,主要就是实现GenericService这个类型的$invoke方法类的实现机制,我们看一看到这个方法的入参就能够得知,我们是可以利用这个方法调用实现任务体系功能的实现机制。
public class RawTypeService implements GenericService {
@Override
public Object $invoke(String methodName, String[] parameterTypes, Object[] args) throws GenericException {
if (method.equals("process")) {
System.out.print("executing method1.");
throw new RuntimeException("method1: throws exception");
} else if (methodName.equals("execute")) {
System.out.print("executing method2.");
return CompletableFuture.completedFuture("method2: result" + args[0]);
} else {
try {
return defaultOperation(method, parameterTypes, args);
} catch (Exception e) {
throw new GenericException(e);
}
}
}
private Object defaultOperation(String method, String[] parameterTypes, Object[] args) throws Exception {
throw new UnsupportedOperationException("method does not exist.");
}
}
在Spring的xml配置模式进行暴漏和注册服务的实现类体系。
<bean id="rawTypeService" class="com.xx.RawTypeService" />
<dubbo:service interface="com.xx.AnnotationService" ref="rawTypeService"/>
其中的AnnotationService类代表着我们的定义的协议接口。
用org.apache.dubbo.rpc.service.GenericService可以替代所有相关的业务接口实现控制
GenericService rawTypeService = new RawTypeService();
ServiceConfig的实例很重,里面封装了所有与注册中心及服务提供方连接,如果使用APi模式,建议请缓存下来。
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("generic-impl-provider");
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress(zookeeperAddress);
ServiceConfig<GenericService> service = new ServiceConfig<>();
service.setApplication(applicationConfig);
service.setRegistry(registryConfig);
service.setInterface("com.xx.AnnotationService");
service.setRef(rawTypeService);
service.setGeneric("true"); // 后面的版本貌似不用再手动指定了,因为后面的版本已经基本上可以服务自己判定。
service.export();
我们通过@DubboService注解进行修饰对应的实现类即可。
@DubboService(interfaceName=“com.xx.AnnotationService")
public class RawTypeService implements GenericService {
@Override
public Object $invoke(String methodName, String[] parameterTypes, Object[] args) throws GenericException {
if (method.equals("method1")) {
System.out.print("executing method1.");
throw new RuntimeException("method1: throws exception");
} else if (methodName.equals("method2")) {
System.out.print("executing method2.");
return CompletableFuture.completedFuture("method2: result" + args[0]);
} else {
try {
return defaultOperation(method, parameterTypes, args);
} catch (Exception e) {
throw new GenericException(e);
}
}
}
private Object defaultOperation(String method, String[] parameterTypes, Object[] args) throws Exception {
throw new UnsupportedOperationException("method does not exist.");
}
}
即可实现相关的dubbo的暴漏消费接口实现机制。
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder/>
<dubbo:application name="generic-impl-consumer"/>
<dubbo:registry address="zookeeper://${zookeeper.address:127.0.0.1}:2181"/>
<dubbo:reference timeout="6000000" id="rawTypeService" check="true"
interface="com.xx.AnnotationService"/>
beans>
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/generic-impl-consumer.xml");
context.start();
AnnotationService annotationService = (AnnotationService) context.getBean("annotationService");
}
@DubboReference(generic=”true“)
private AnnotationService helloService;
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("generic-call-consumer");
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
ReferenceConfig<GenericService> referenceConfig = new ReferenceConfig<>();
referenceConfig.setInterface("com.xx.AnnotationService");
applicationConfig.setRegistry(registryConfig);
referenceConfig.setApplication(applicationConfig);
referenceConfig.setGeneric(true);
referenceConfig.setTimeout(7000);
genericService = referenceConfig.get();
客户端泛化调用是指在调用方没有服务方提供的API(SDK)的情况下,对服务方进行调用,并且可以正常拿到调用结果,当然也可能进行动态调用对应的方法和对应的服务接口。
泛化调用主要用于实现一个通用的远程服务框架,可通过实现GenericService接口处理所有服务请求。比如如下场景:
如果要搭建一个网关服务,那么服务网关要作为所有RPC服务的调用端。但是网关本身不应该依赖于服务提供方的接口API(这样会导致每有一个新的服务发布,就需要修改网关的代码以及重新部署),所以需要泛化调用的支持。
如果要搭建一个可以测试RPC调用的平台,用户输入分组名、接口、方法名等信息,就可以测试对应的RPC服务。那么由于同样的原因(即会导致每有一个新的服务发布,就需要修改网关的代码以及重新部署),所以平台本身不应该依赖于服务提供方的接口API。所以需要泛化调用的支持。
@Slf4j
public class RawTypeService implements AnnotationService {
@override
public String method1(String name){
log.info("method1:{}",name);
}
@override
public String method2(String name){
log.info("method2:{}",name);
}
}
在设置ServiceConfig时,使用setGeneric(“true”)来开启泛化调用
在设置ServiceConfig时,使用setRef指定实现类时,要设置一个GenericService 的对象,而不是真正的服务实现类对象。
其他设置与正常Api服务启动一致即可
public static void main(String[] args) throws Exception {
new EmbeddedZooKeeper(2181, false).start();
//创建ApplicationConfig
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("generic-impl-provider");
//创建注册中心配置
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress(zookeeperAddress);
//新建服务实现类,注意要使用GenericService接收
AnnotationService helloService = new RawTypeService();
//创建服务相关配置
ServiceConfig<AnnotationService> service = new ServiceConfig<>();
service.setApplication(applicationConfig);
service.setRegistry(registryConfig);
service.setInterface("com.xx.AnnotationService");
service.setRef(helloService);
//重点:设置为泛化调用
//注:不再推荐使用参数为布尔值的setGeneric函数
service.setGeneric("true");
service.export();
System.out.println("dubbo service started");
new CountDownLatch(1).await();
}
}
在设置 ReferenceConfig 时,使用 setGeneric(“true”) 来开启泛化调用
配置完 ReferenceConfig 后,使用 referenceConfig.get() 获取到 GenericService类的实例
使用其 $invoke 方法获取结果
其他设置与正常 Api 服务启动一致即可
public static void main(String[] args) throws Exception {
//创建ApplicationConfig
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("generic-call-consumer");
//创建注册中心配置
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
//创建服务引用配置
ReferenceConfig<GenericService> referenceConfig = new ReferenceConfig<>();
//设置接口
referenceConfig.setInterface("com.xx.AnnotationService");
applicationConfig.setRegistry(registryConfig);
referenceConfig.setApplication(applicationConfig);
//重点:设置为泛化调用
//注:不再推荐使用参数为布尔值的setGeneric函数
//应该使用referenceConfig.setGeneric("true")代替
referenceConfig.setGeneric(true);
//设置超时时间
referenceConfig.setTimeout(7000);
//获取服务,由于是泛化调用,所以获取的一定是GenericService类型
GenericService genericService = referenceConfig.get();
//使用GenericService类对象的$invoke方法可以代替原方法使用
//第一个参数是需要调用的方法名
//第二个参数是需要调用的方法的参数类型数组,为String数组,里面存入参数的全类名。
//第三个参数是需要调用的方法的参数数组,为Object数组,里面存入需要的参数。
Object result = genericService.$invoke("method1", new String[]{"java.lang.String"}, new Object[]{"world"});
//使用CountDownLatch,如果使用同步调用则不需要这么做。
CountDownLatch latch = new CountDownLatch(1);
//获取结果
CompletableFuture<String> future = RpcContext.getContext().getCompletableFuture();
future.whenComplete((value, t) -> {
System.err.println("invoke(whenComplete): " + value);
latch.countDown();
});
//打印结果
System.err.println("invoke(return): " + result);
latch.await();
}
Spring中服务暴露与服务发现有多种使用方式,如xml,注解。这里以xml为例。 步骤:
消费者端原有的 dubbo:reference 标签加上 generic=true 的属性。
<dubbo:reference id="annotationService" generic = "true" interface="com.xx.AnnotationService"/>
@DubboReference(interface="com.xx.AnnotationService")
GenericService genericService;
获取到 Bean 容器,通过 Bean 容器拿到 GenericService 实例。调用 $invoke 方法获取结果
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/generic-impl-consumer.xml");
context.start();
//服务对应bean的名字由xml标签的id决定
GenericService genericService = context.getBean("annotationService");
//获得结果
Object result = genericService.$invoke("method1", new String[]{"java.lang.String"}, new Object[]{"world"});
@DubboReference(interfaceClass = CommonRpcApi.class)
GenericService commonRpcApi2;
@GetMapping("/generic/consumer")
public ResponseEntity