Generic Reference
实现一个通用的服务测试框架,可通过 GenericService
调用所有服务实现。泛化接口调用方式主要用于客户端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 Map
表示,通常用于框架集成,比如:实现一个通用的服务测试框架,可通过 GenericService
调用所有服务实现。基本类型以及Date,List,Map等不需要转换,直接调用 。
<dubbo:reference id="userService"
interface="com.baizhi.service.IUserService" group="*" generic="true" >
<dubbo:method name="save" merger="false"/>
</dubbo:reference>
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("application.xml");
ctx.start();
GenericService userService = (GenericService) ctx.getBean("userService");
HashMap<String, Object> params = new HashMap<String, Object>();
params.put("class","com.baizhi.entities.User");
params.put("id",1);
params.put("name","jiangzz");
params.put("birthDay",new Date());
//参数名 参数类型 参数
userService.$invoke("save",new String[]{"com.baizhi.entities.User"},new Object[]{params});
ctx.stop();
Dubbo的参数验证无法验证泛华引用类型。
Java API调用
ReferenceConfig<GenericService> referenceConfig=new ReferenceConfig<>();
referenceConfig.setInterface("com.baizhi.service.IUserService");
referenceConfig.setGroup("*");
referenceConfig.setGeneric(true);
HashMap<String, Object> params = new HashMap<String, Object>();
params.put("class","com.baizhi.entities.User");
params.put("id",1);
params.put("name","jiangzz");
params.put("birthDay",new Date());
params.put("sex",false);
referenceConfig.get().$invoke("save",new String[]{"com.baizhi.entities.User"},new Object[]{params});
Protobuf
当前 Dubbo 的服务定义和具体的编程语言绑定,没有提供一种语言中立的服务描述格式,比如 Java 就是定义 Interface 接口,到了其他语言又得重新以另外的格式定义一遍。 2.7.5 版本通过支持 Protobuf IDL 实现了语言中立的服务定义。日后,不论我们使用什么语言版本来开发 Dubbo 服务,都可以直接使用 IDL 定义如下服务。
①在项目java平级的目录下创建proto文件夹,然后在该文件夹下创建DemoService.proto
文本文件
syntax = "proto3";
option java_package = "com.baizhi.proto";
option java_multiple_files = true;
option java_outer_classname = "DemoServiceProto";
option optimize_for = SPEED;
// The demo service definition.
service DemoService {
rpc query (Request) returns (Response){}
}
// The request message containing the user's name.
message Request {
string name = 1;
}
// The response message containing the greetings
message Response {
string message = 1;
}
②在消费端和服务端分别引入以下依赖
<!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-serialization-protobuf -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-serialization-protobuf</artifactId>
<version>2.7.8</version>
</dependency>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.1</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier}</protocArtifact>
<outputDirectory>src/main/java</outputDirectory>
<clearOutputDirectory>false</clearOutputDirectory>
<protocPlugins>
<protocPlugin>
<id>dubbo</id>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-compiler</artifactId>
<version>0.0.1</version>
<mainClass>org.apache.dubbo.gen.dubbo.DubboGenerator</mainClass>
</protocPlugin>
</protocPlugins>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
③执行protobuf:compile
指令分别在消费端和服务提供端生成相关的代码
④在服务端编写DemoService的实现
public class ProtoBufDemoService implements DemoServiceDubbo.IDemoService {
@Override
public Response query(Request request) {
System.out.println("接收来自客户端消息:"+request.getName());
Response.Builder builder = Response.newBuilder();
builder.setMessage("服务端响应");
return builder.build();
}
@Override
public CompletableFuture<Response> queryAsync(Request request) {
return CompletableFuture.completedFuture(query(request));
}
}
⑤配置该Bean,并且使用Dubbo暴露该服务
<bean id="protoBufDemoService" class="com.baizhi.service.impl.ProtoBufDemoService"/>
<dubbo:service interface="com.baizhi.proto.DemoServiceDubbo$IDemoService" ref="protoBufDemoService"/>
注意这里的serialization属性配置的是元素的序列化方式,这里我们必须选择protobuf即可
⑥配置服务引用
<dubbo:reference id="demoService" interface="com.baizhi.proto.DemoServiceDubbo$IDemoService"/>
⑦测试调用远程服务
DemoServiceDubbo.IDemoService demoService = (DemoServiceDubbo.IDemoService) ctx.getBean("demoService");
Request req = Request.newBuilder().setName("测试用户").build();
Response response = demoService.query(req);
System.out.println(response.getMessage());
Generic Service
泛接口实现方式主要用于服务器端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 Map 表示,通常用于框架集成,比如:实现一个通用的远程服务 Mock 框架,可通过实现 GenericService 接口处理所有服务请求。
①编写泛化服务bin注册
public class UserDefineGenericService implements GenericService {
@Override
public Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException {
if ("sayHello".equals(method)) {
return "Welcome " + args[0];
}else{
return null;
}
}
}
<bean id="genericService" class="com.baizhi.service.impl.UserDefineGenericService"/>
<dubbo:service interface="com.baizhi.service.impl.XxxService" ref="genericService"/>
②泛化服务调用
<dubbo:reference id="xxxService" generic="true" interface="com.baizhi.service.impl.XxxService"/>
GenericService genericSerivce = (GenericService) ctx.getBean("xxxService");
Object result=genericSerivce.$invoke("sayHello",new String[]{"java.lang.String"},new Object[]{"张三"});
System.out.println(result);
Echo Test
通过回声测试检测 Dubbo 服务是否可用,回声测试用于检测服务是否可用,回声测试按照正常请求流程执行,能够测试整个调用是否通畅,可用于监控。所有服务自动实现 EchoService
接口,只需将任意服务引用强制转型为 EchoService
,即可使用。
<dubbo:reference id="userService" interface="com.baizhi.service.IUserService"
registry="zk_registry"
application="app1"
timeout="1000"
retries="4"
cluster="failover"
loadbalance="consistenthash"
group="*"
validation="true">
<dubbo:method name="save" timeout="5000" loadbalance="consistenthash" merger="true" validation="true" />
<dubbo:method name="queryAll" timeout="5000" loadbalance="consistenthash" merger="true" cache="jcache" />
<dubbo:method name="queryUserById" timeout="5000" loadbalance="consistenthash" merger=".mergerUser" />
</dubbo:reference>
EchoService echoService = (EchoService) ctx.getBean("userService");
Object result = echoService.$echo("hello");
System.out.println(result);
这里需要注意仅仅只有非泛化的引用才能使用回声测试。
Context
通过上下文存放当前调用过程中所需的环境信息,上下文中存放的是当前调用过程中所需的环境信息。所有配置信息都将转换为 URL 的参数,参见 schema 配置参考手册 中的对应URL参数一列。RpcContext 是一个 Thread Local的临时状态记录器,当接收到 RPC 请求,或发起 RPC 请求时,RpcContext 的状态都会变化。比如:A 调 B,B 再调 C,则 B 机器上,在 B 调 C 之前,RpcContext 记录的是 A 调 B 的信息,在 B 调 C 之后,RpcContext 记录的是 B 调 C 的信息。
①服务消费方
// 远程调用
xxxService.xxx();
// 本端是否为消费端,这里会返回true
boolean isConsumerSide = RpcContext.getContext().isConsumerSide();
// 获取最后一次调用的提供方IP地址
String serverIP = RpcContext.getContext().getRemoteHost();
// 获取当前服务配置信息,所有配置信息都将转换为URL的参数
String application = RpcContext.getContext().getUrl().getParameter("application");
// 注意:每发起RPC调用,上下文状态会变化
yyyService.yyy();
②服务提供方
// 本端是否为提供端,这里会返回true
boolean isProviderSide = RpcContext.getContext().isProviderSide();
// 获取调用方IP地址
String clientIP = RpcContext.getContext().getRemoteHost();
// 获取当前服务配置信息,所有配置信息都将转换为URL的参数
String application = RpcContext.getContext().getUrl().getParameter("application");
// 注意:每发起RPC调用,上下文状态会变化
yyyService.yyy();
// 此时本端变成消费端,这里会返回false
boolean isProviderSide = RpcContext.getContext().isProviderSide();
Implicit parameters
通过 Dubbo 中的 Attachment 在服务消费方和提供方之间隐式传递参数,可以通过 RpcContext
上的 setAttachment
和 getAttachment
在服务消费方和提供方之间进行参数的隐式传递。
path, group, version, dubbo, token, timeout 几个 key 是保留字段,请使用其它值。
①在服务消费方端设置隐式参数
RpcContext.getContext().setAttachment("index", "1"); // 隐式传参,后面的远程调用都会隐式将这些参数发送到服务器端,类似cookie,用于框架集成,不建议常规业务使用
xxxService.xxx(); // 远程调用
// ...
②在服务提供方端获取隐式参数
public class XxxServiceImpl implements XxxService {
public void xxx() {
// 获取客户端隐式传入的参数,用于框架集成,不建议常规业务使用
String index = RpcContext.getContext().getAttachment("index");
}
}
Callback parameter
通过参数回调从服务器端调用客户端逻辑,参数回调方式与调用本地 callback 或 listener 相同,只需要在 Spring 的配置文件中声明哪个参数是 callback 类型即可。Dubbo 将基于长连接生成反向代理,这样就可以从服务器端调用客户端逻辑。
public interface IUserService {
...
public void saveUserWithCallBack(User user ,UserCallback userCallback);
}
public interface UserCallback {
public User modifyUser(User user);
}
①服务端实现
public class UserServiceImp01 implements IUserService {
//...
public void saveUserWithCallBack(User user, UserCallback userCallback) {
System.out.println("saveUserWithCallBack:"+user);
user.setName("zhangsan");
user=userCallback.modifyUser(user);
System.out.println(user);
}
}
<dubbo:service interface="com.baizhi.service.IUserService" ref="userService01"
protocol="dubbo_protocol"
application="app1"
registry="zk_registry"
retries="2"
weight="100"
group="g1"
loadbalance="random">
...
<!--配置回调参数-->
<dubbo:method name="saveUserWithCallBack" >
<dubbo:argument index="1" callback="true"/>
</dubbo:method>
</dubbo:service>
②消费端实现
IUserService userSerivce = ctx.getBean("userService", IUserService.class);
userSerivce.saveUserWithCallBack(new User(), new UserCallback() {
@Override
public User modifyUser(User user) {
System.out.println("服务端回调");
user.setName(user.getName()+"_客户端追加!");
return user;
}
});
可以简单的理解为当设置为回调参数的时候,相当于服务端那边创建一个代理直接回调客户端的代码,继而实现客户端向服务端传递代码逻辑!
Event Notification
在调用之前、调用之后、出现异常时的时间通知,在调用之前、调用之后、出现异常时,会触发 oninvoke
、onreturn
、onthrow
三个事件,可以配置当事件发生时,通知哪个类的哪个方法。
public interface Notify {
public void onreturn(Object msg, Integer id);
public void onthrow(Throwable ex, Integer id);
public void oninvoke(Integer id);
}
public class UserNotify implements Notify {
public void onreturn(Object msg, Integer id) {
System.out.println("onreturn"+"\t"+msg+"\t"+id);
}
public void onthrow(Throwable ex, Integer id) {
System.out.println("onthrow:"+ex+"\t"+id);
}
public void oninvoke(Integer id) {
System.out.println("oninvoke:"+id);
}
}
①消费者引用消费服务
<dubbo:reference id="userService" interface="com.baizhi.service.IUserService"
registry="zk_registry"
application="app1"
timeout="1000"
retries="4"
cluster="failover"
loadbalance="consistenthash"
group="g1"
validation="true">
<dubbo:method name="queryUserById"
onreturn="notify.onreturn"
onthrow="notify.onthrow"
oninvoke="notify.oninvoke" />
</dubbo:reference>
这里面有个bug,如果消费者这边存在同时merger多个分组的结果,此时系统不会做时间通知。