curl -G https://start.vertx.io/starter.zip -d "groupId=io.vertx.howtos" -d "artifactId=protobuf-eventbus-howto" -d "packageName=io.vertx.howtos.protobuf.eventbus" -d "vertxDependencies=vertx-hazelcast" -d "jdkVersion=11" -d "buildTool=maven" --output protobuf-eventbus-howto.zip
unzip -d protobuf-eventbus-howto protobuf-eventbus-howto.zip
事件总线 (Event Bus)
在 Event Bus 事件总线上交换传输 Protobuf 对象
本文档将向你展示如何在 Vert.x Event Bus 事件总线上直接收发由 Protocol Buffers (Protobuf) 生成的强类型消息。
你将构建什么
你将构建一个周期性生成问候消息的分布式应用程序,主要包含:
-
一个负责发出
GreetingRequest消息的发送端 Verticle; -
一个接收请求并以
GreetingResponse进行回复的接收端 Verticle。
前置要求
-
文本编辑器或 IDE
-
Java 11 或更高版本
创建工程项目
打开 https://start.vertx.io 并展开高级选项:
-
Group Id: 设置为
io.vertx.howtos -
Artifact Id: 设置为
protobuf-eventbus-howto -
Dependencies: 添加
Hazelcast Cluster Manager -
Package: 设置为
io.vertx.howtos.protobuf.eventbus
或者通过命令行一键生成:
定义 Protobuf 契约
在 src/main/proto/greetings.proto 中定义消息格式:
syntax = "proto3";
package greeting;
option java_multiple_files = true;
option java_package = "io.vertx.howtos.protobuf.eventbus";
option java_outer_classname = "GreetingProtos";
message GreetingRequest {
string name = 1;
}
message GreetingReply {
string message = 1;
}
实现消息编解码器
为了让 Event Bus 识别 Protobuf 生成的 Java 类,必须实现 Vert.x 的 MessageCodec 接口:
package io.vertx.howtos.protobuf.eventbus;
import com.google.protobuf.GeneratedMessageV3;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.eventbus.MessageCodec;
import io.vertx.core.impl.SerializableUtils;
public class ProtobufCodec implements MessageCodec<GeneratedMessageV3, GeneratedMessageV3> {
static final String PROTOS_PACKAGE_NAME = "io.vertx.howtos.protobuf.eventbus.";
@Override
public void encodeToWire(Buffer buffer, GeneratedMessageV3 o) {
var bytes = SerializableUtils.toBytes(o);
buffer.appendInt(bytes.length);
buffer.appendBytes(bytes);
}
@Override
public GeneratedMessageV3 decodeFromWire(int pos, Buffer buffer) {
var length = buffer.getInt(pos);
pos += 4;
var bytes = buffer.getBytes(pos, pos + length);
return (GeneratedMessageV3) SerializableUtils.fromBytes(bytes, CheckedClassNameObjectInputStream::new);
}
@Override
public GeneratedMessageV3 transform(GeneratedMessageV3 o) {
return o;
}
@Override
public String name() {
return "ProtobufCodec";
}
@Override
public byte systemCodecID() {
return -1; // -1 for a user codec
}
public boolean appliesTo(String className) {
return className.startsWith(PROTOS_PACKAGE_NAME);
}
}
在自定义 Launcher 中注册编解码器:
package io.vertx.howtos.protobuf.eventbus;
import io.vertx.launcher.application.HookContext;
import io.vertx.launcher.application.VertxApplication;
import io.vertx.launcher.application.VertxApplicationHooks;
public class CustomLauncher extends VertxApplication implements VertxApplicationHooks {
public CustomLauncher(String[] args) {
super(args);
}
public static void main(String[] args) {
new CustomLauncher(args).launch();
}
@Override
public void afterVertxStarted(HookContext context) {
var vertx = context.vertx();
var protobufCodec = new ProtobufCodec();
vertx.eventBus().registerCodec(protobufCodec);
vertx.eventBus().codecSelector(body -> {
return protobufCodec.appliesTo(body.getClass().getName()) ? protobufCodec.name() : null;
});
}
}
实现发送者与接收者 Verticle
发送端 Verticle
package io.vertx.howtos.protobuf.eventbus;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.eventbus.Message;
public class SenderVerticle extends AbstractVerticle {
@Override
public void start() throws Exception {
vertx.setPeriodic(5000, l -> {
var request = GreetingRequest.newBuilder().setName("Jane Doe").build();
System.out.printf("Sending request = %s (%d)%n", request.getName(), System.identityHashCode(request));
vertx.eventBus().<GreetingReply>request("greetings", request)
.map(Message::body)
.onFailure(Throwable::printStackTrace)
.onSuccess(reply -> System.out.printf("Received reply = %s (%d)%n", reply.getMessage(), System.identityHashCode(reply)));
});
}
}
接收端 Verticle
package io.vertx.howtos.protobuf.eventbus;
import io.vertx.core.Future;
import io.vertx.core.VerticleBase;
public class ReceiverVerticle extends VerticleBase {
@Override
public Future<?> start() {
return vertx.eventBus().<GreetingRequest>consumer("greetings", msg -> {
var request = msg.body();
System.out.printf("Received request = %s (%d)%n", request.getName(), System.identityHashCode(request));
var greeting = String.format("Hello %s", request.getName());
var reply = GreetingReply.newBuilder().setMessage(greeting).build();
System.out.printf("Sending reply = %s (%d)%n", reply.getMessage(), System.identityHashCode(reply));
msg.reply(reply);
}).completion();
}
}
运行与验证
打开两个独立的终端窗口,分别启动接收端和发送端:
终端 1(接收端):
./mvnw compile exec:java -Dexec.mainClass=io.vertx.howtos.protobuf.eventbus.CustomLauncher -Dexec.args="run io.vertx.howtos.protobuf.eventbus.ReceiverVerticle -cluster"
终端 2(发送端):
./mvnw compile exec:java -Dexec.mainClass=io.vertx.howtos.protobuf.eventbus.CustomLauncher -Dexec.args="run io.vertx.howtos.protobuf.eventbus.SenderVerticle -cluster"
两个节点通过 Hazelcast 自动组网发现,你将在终端 2 看到接收端返回的清晰响应!
总结
本文档展示了:
-
为 Google Protocol Buffers 编写通用的 Vert.x
MessageCodec消息编解码器; -
在集群环境下的 Event Bus 上安全高效传输二进制序列化对象。