Go-Java 互通示例

准备工作

环境

JDK 8,Golang >= 1.15,Dubbo 3.0.2,zookeeper 启动,

Go- Java 互通前提

  • Go/Java 定义的传输结构一致

    • PB 序列化

    proto for Go

    1. // The response message containing the greetings
    2. message User {
    3. string name = 1;
    4. string id = 2;
    5. int32 age = 3;
    6. }

    proto for Java

    1. // The response message containing the greetings
    2. message User {
    3. string name = 1;
    4. string id = 2;
    5. int32 age = 3;
    6. }
    • Hessian 序列化

    POJO for Go,需参考 Dubbogo Hessian 序列化支持文档

    1. type User struct {
    2. ID string
    3. Name string
    4. Age int32
    5. }
    6. func (u *User) JavaClassName() string {
    7. return "org.apache.dubbo.User"
    8. }
    9. func init(){
    10. hessian.RegisterPOJO(&User{})
    11. }

    POJO for Java

    1. package org.apache.dubbo
    2. public class User {
    3. private String id;
    4. private String name;
    5. private int age;
    6. }
  • Java 需要互通的方法签名与 Go 一致

    例如:

    Java Interface

    1. public interface IGreeter {
    2. /**
    3. * <pre>
    4. * Sends a greeting
    5. * </pre>
    6. */
    7. User sayHello(HelloRequest request);
    8. }

    Go client (由protoc-gen-go-triple 根据 proto 文件自动生成)

    1. type GreeterClientImpl struct {
    2. // Sends a greeting
    3. SayHello func(ctx context.Context, in *HelloRequest) (*User, error)
    4. }

    Go server (由开发者定义)

    1. type GreeterProvider struct {
    2. api.GreeterProviderBase
    3. }
    4. func (s *GreeterProvider) SayHello(ctx context.Context, in *api.HelloRequest) (*api.User, error) {
    5. logger.Infof("Dubbo3 GreeterProvider get user name = %s\n", in.Name)
    6. return &api.User{Name: "Hello " + in.Name, Id: "12345", Age: 21}, nil
    7. }

    Go 方法需要遵守 Dubbogo 3.0 用户服务接口定义规范

  • Java 的三元组与Go service/reference 配置的 interface 一致

    三元组,即为接口级别配置的:interface, group, version。其中需要注意,group 和 version 的概念为 dubbo 接口的 group 和vesion,在启动 dubbo-java 服务时配置于 spring cloud 的 properties 文件中,并非pom.xml 中 mvn 依赖的version。 group 和version 默认为空,在 dubbo-go 框架中,可以在service/reference 的对应位置指定 group 和 version。

    例如:

    Java 的接口全名:com.apache.dubbo.sample.basic.IGreeter,接口 version 为v1.0.1, group 为

    Go-client:

    1. references:
    2. GreeterClientImpl:
    3. protocol: tri
    4. interface: com.apache.dubbo.sample.basic.IGreeter # must be compatible with grpc or dubbo-java
    5. group: dubbogo # 需要与服务端对应 默认为空
    6. version: v1.0.1 # 需要与服务端对应 默认为空

    Go-server:

    1. services:
    2. GreeterProvider:
    3. protocol-ids: tripleProtocol
    4. interface: com.apache.dubbo.sample.basic.IGreeter # must be compatible with grpc or dubbo-java
    5. group: dubbogo # 需要与服务端对应 默认为空
    6. version: v1.0.1 # 需要与服务端对应 默认为空

1. 基于 Triple 协议互通 (PB序列化)

参考 dubbo-go-samples/helloworld

1.1 Go-Client -> Java-Server

Java-Server 启动

  1. 定义 Java 的 PB 文件,可参考 Dubbo 快速开始
  1. syntax = "proto3";
  2. option java_package = "org.apache.dubbo.sample.hello";
  3. package helloworld;
  4. // The request message containing the user's name.
  5. message HelloRequest {
  6. string name = 1;
  7. }
  8. // The response message containing the greetings
  9. message User {
  10. string name = 1;
  11. string id = 2;
  12. int32 age = 3;
  13. }

该接口描述文件定义了将会生成的 Java 类 org.apache.dubbo.sample.hello.Helloworld,以及类中包含的传输结构 HelloRequest 和 User 类。

  1. 定义服务接口:

    com.apache.dubbo.sample.basic.IGreeter

  1. package com.apache.dubbo.sample.basic;
  2. // 引入根据 PB 生成的类
  3. import org.apache.dubbo.sample.hello.Helloworld.User;
  4. import org.apache.dubbo.sample.hello.Helloworld.HelloRequest;
  5. public interface IGreeter {
  6. /**
  7. * <pre>
  8. * Sends a greeting
  9. * </pre>
  10. */
  11. // 定义接口
  12. User sayHello(HelloRequest request);
  13. }
  1. 实现服务接口:

    IGreeter1Impl.java

  1. package com.apache.dubbo.sample.basic;
  2. import org.apache.dubbo.sample.hello.Helloworld.User;
  3. import org.apache.dubbo.sample.hello.Helloworld.HelloRequest;
  4. public class IGreeter1Impl implements IGreeter {
  5. @Override
  6. public User sayHello(HelloRequest request) {
  7. System.out.println("receiv: " + request);
  8. User usr = User.newBuilder()
  9. .setName("hello " + request.getName())
  10. .setAge(18)
  11. .setId("12345").build();
  12. return usr;
  13. }
  14. }
  1. 使用 Dubbo3 框架启动服务

    ApiProvider.java

  1. package com.apache.dubbo.sample.basic;
  2. import org.apache.dubbo.common.constants.CommonConstants;
  3. import org.apache.dubbo.config.ApplicationConfig;
  4. import org.apache.dubbo.config.ProtocolConfig;
  5. import org.apache.dubbo.config.RegistryConfig;
  6. import org.apache.dubbo.config.ServiceConfig;
  7. import java.util.concurrent.CountDownLatch;
  8. public class ApiProvider {
  9. public static void main(String[] args) throws InterruptedException {
  10. ServiceConfig<IGreeter> service = new ServiceConfig<>();
  11. service.setInterface(IGreeter.class);
  12. service.setRef(new IGreeter1Impl());
  13. // 使用 Triple 协议
  14. service.setProtocol(new ProtocolConfig(CommonConstants.TRIPLE, 50051));
  15. service.setApplication(new ApplicationConfig("demo-provider"));
  16. // 使用 ZK 作为注册中心
  17. service.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
  18. service.export();
  19. System.out.println("dubbo service started");
  20. new CountDownLatch(1).await();
  21. }
  22. }

启动服务,可看到输出如下日志,代表 Java Triple Server 启动成功

  1. main INFO bootstrap.DubboBootstrap: [DUBBO] DubboBootstrap has started., dubbo version: 3.0.2, current host: 192.168.0.108
  2. dubbo service started

Go-Client 启动

对于已经启动的Dubbo服务,如需要开发与其对应的Go-client,需要进行如下步骤:

  1. 编写与 Java 适配的 proto文件

    samples_api.proto

  1. syntax = "proto3";
  2. package api; // pacakge 名随意指定
  3. // necessary
  4. option go_package = "./;api";
  5. // The greeting service definition.
  6. service Greeter {
  7. // Sends a greeting
  8. rpc SayHello (HelloRequest) returns (User) {}
  9. }
  10. // The request message containing the user's name.
  11. message HelloRequest {
  12. string name = 1;
  13. }
  14. // The response message containing the greetings
  15. message User {
  16. string name = 1;
  17. string id = 2;
  18. int32 age = 3;
  19. }
  1. 使用 protoc-gen-triple 生成接口文件
  1. protoc -I . samples_api.proto --triple_out=plugins=triple:.
  1. 撰写配置文件: dubbogo.yml
  1. dubbo:
  2. registries:
  3. demoZK:
  4. protocol: zookeeper
  5. address: 127.0.0.1:2181
  6. consumer:
  7. references:
  8. GreeterClientImpl:
  9. protocol: tri
  10. interface: com.apache.dubbo.sample.basic.IGreeter # must be compatible with grpc or dubbo-java
  1. 撰写 main.go 文件,发起调用
  1. // 引入生成的接口结构
  2. var grpcGreeterImpl = new(api.GreeterClientImpl)
  3. // export DUBBO_GO_CONFIG_PATH=dubbogo.yml
  4. func main() {
  5. config.SetConsumerService(grpcGreeterImpl)
  6. if err := config.Load(); err != nil {
  7. panic(err)
  8. }
  9. time.Sleep(3 * time.Second)
  10. logger.Info("start to test dubbo")
  11. req := &api.HelloRequest{
  12. Name: "laurence",
  13. }
  14. reply, err := grpcGreeterImpl.SayHello(context.Background(), req)
  15. if err != nil {
  16. logger.Error(err)
  17. }
  18. logger.Infof("client response result: %v\n", reply)
  19. }
  1. 可查看到调用成功的日志
  • go-client
  1. cmd/client.go:53 client response result: name:"hello laurence" id:"12345" age:18
  • java-server
  1. receiv: name: "laurence"

1.2 Java-Client -> Go-Server

Go-Server 启动

  1. 定义配置文件
  1. dubbo:
  2. registries:
  3. demoZK:
  4. protocol: zookeeper
  5. address: 127.0.0.1:2181
  6. protocols:
  7. triple:
  8. name: tri
  9. port: 20000
  10. provider:
  11. services:
  12. GreeterProvider:
  13. interface: com.apache.dubbo.sample.basic.IGreeter # must be compatible with grpc or dubbo-java
  1. 引入传输结构,定义服务
  1. type GreeterProvider struct {
  2. api.GreeterProviderBase
  3. }
  4. func (s *GreeterProvider) SayHello(ctx context.Context, in *api.HelloRequest) (*api.User, error) {
  5. logger.Infof("Dubbo3 GreeterProvider get user name = %s\n", in.Name)
  6. return &api.User{Name: "Hello " + in.Name, Id: "12345", Age: 21}, nil
  7. }
  1. 启动服务
  1. // export DUBBO_GO_CONFIG_PATH=dubbogo.yml
  2. func main() {
  3. config.SetProviderService(&GreeterProvider{})
  4. if err := config.Load(); err != nil {
  5. panic(err)
  6. }
  7. select {}
  8. }

Java-Client 启动

  1. proto 文件编写和接口生成参考上述 java-server 介绍

  2. 启动Consumer

    ApiCnosumer.java

  1. public class ApiConsumer {
  2. public static void main(String[] args) throws InterruptedException, IOException {
  3. ReferenceConfig<IGreeter> ref = new ReferenceConfig<>();
  4. ref.setInterface(IGreeter.class);
  5. ref.setCheck(false);
  6. ref.setProtocol(CommonConstants.TRIPLE);
  7. ref.setLazy(true);
  8. ref.setTimeout(100000);
  9. ref.setApplication(new ApplicationConfig("demo-consumer"));
  10. ref.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
  11. final IGreeter iGreeter = ref.get();
  12. System.out.println("dubbo ref started");
  13. Helloworld.HelloRequest req = Helloworld.HelloRequest.newBuilder().setName("laurence").build();
  14. try {
  15. final Helloworld.User reply = iGreeter.sayHello(req);
  16. TimeUnit.SECONDS.sleep(1);
  17. System.out.println("Reply:" + reply);
  18. } catch (Throwable t) {
  19. t.printStackTrace();
  20. }
  21. System.in.read();
  22. }
  23. }

2. 基于 Dubbo 协议互通 (Hessian2序列化)

参考 dubbo-go-samples/rpc/dubbo

2.1 Go-Client -> Java-Server

Java-Server 启动

  1. 定义 Java 接口、参数和返回值,可参考 Dubbo 快速开始
  1. package org.apache.dubbo;
  2. // 需要暴露的服务接口
  3. public interface UserProvider {
  4. User getUser(int usercode);
  5. }
  1. package org.apache.dubbo;
  2. public class User implements Serializable {
  3. private String id;
  4. private String name;
  5. private int age;
  6. private Date time = new Date();
  7. /* ... */
  8. }
  1. 实现服务接口:

UserProviderImpl.java

  1. package org.apache.dubbo;
  2. public class UserProviderImpl implements UserProvider {
  3. public User getUser(int userCode) {
  4. return new User(String.valueOf(userCode), "userCode get", 48);
  5. }
  6. }
  1. 使用SpringBoot 启动

Provider.java

  1. package org.apache.dubbo;
  2. // use when config by API
  3. /*
  4. import java.util.concurrent.CountDownLatch;
  5. import org.apache.dubbo.common.constants.CommonConstants;
  6. import org.apache.dubbo.config.ApplicationConfig;
  7. import org.apache.dubbo.config.ProtocolConfig;
  8. import org.apache.dubbo.config.RegistryConfig;
  9. import org.apache.dubbo.config.ServiceConfig;
  10. */
  11. import org.springframework.context.support.ClassPathXmlApplicationContext;
  12. public class Provider {
  13. // main function, config from spring boot
  14. public static void main(String[] args) throws Exception {
  15. ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo.provider.xml"});
  16. context.start();
  17. System.in.read(); // press any key to exit
  18. }
  19. // config by API
  20. // public static void startComplexService() throws InterruptedException {
  21. // ServiceConfig<ComplexProvider> service = new ServiceConfig<>();
  22. // service.setInterface(ComplexProvider.class);
  23. // service.setRef(new ComplexProviderImpl());
  24. // service.setProtocol(new ProtocolConfig(CommonConstants.DUBBO_PROTOCOL, 20001));
  25. // service.setApplication(new ApplicationConfig("demo-provider"));
  26. // service.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
  27. // service.export();
  28. // System.out.println("dubbo service started");
  29. // new CountDownLatch(1).await();
  30. // }
  31. }
  1. 通过Spring 配置 Dubbo 参数

    Resources/META-INF.spring/dubbo.provider.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!--
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. -->
  13. <beans xmlns="http://www.springframework.org/schema/beans"
  14. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  15. xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
  16. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
  17. http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
  18. <!-- 应用名 -->
  19. <dubbo:application name="user-info-server"/>
  20. <!-- 连接到哪个本地注册中心 -->
  21. <dubbo:registry id="dubbogo" address="zookeeper://127.0.0.1:2181" />
  22. <!-- 用dubbo协议在20880端口暴露服务 -->
  23. <dubbo:protocol id="dubbo" name="dubbo" host="127.0.0.1" port="20010" />
  24. <!-- 声明需要暴露的服务接口 -->
  25. <dubbo:service id="aaa" registry="dubbogo" timeout="3000" interface="org.apache.dubbo.UserProvider" ref="demoService"/>
  26. <dubbo:service id="bbb" registry="dubbogo" timeout="3000" interface="org.apache.dubbo.UserProvider" ref="otherService" version="2.0"/>
  27. <dubbo:service id="ccc" registry="dubbogo" timeout="3000" interface="org.apache.dubbo.UserProvider" ref="otherService" group="as" version="2.0"/>
  28. <bean id="demoService" class="org.apache.dubbo.UserProviderImpl" />
  29. <bean id="otherService" class="org.apache.dubbo.UserProviderAnotherImpl"/>
  30. </beans>

启动Provider类,可看到输出如下日志,代表 Dubbo Server 启动成功

  1. [DUBBO] DubboBootstrap is ready., dubbo version: 2.7.7, current host: 127.0.0.1
  2. [DUBBO] DubboBootstrap has started., dubbo version: 2.7.7, current host: 127.0.0.1

Go-Client 启动

对于已经启动的Dubbo服务,如需要开发与其对应的Go-client,需要进行如下步骤:

  1. 编写与 Java 适配的 POJO 类 User
  1. import(
  2. hessian "github.com/apache/dubbo-go-hessian2"
  3. )
  4. // 字段需要与 Java 侧对应,首字母大写
  5. type User struct {
  6. ID string
  7. Name string
  8. Age int32
  9. Time time.Time
  10. }
  11. func (u *User) JavaClassName() string {
  12. return "org.apache.dubbo.User" // 需要与 Java 侧 User 类名对应
  13. }
  14. func init(){
  15. hessian.RegisterPOJO(&pkg.User{}) // 注册 POJO
  16. }
  1. 编写与 Java 侧一致的客户端存根类,其接口方法需要与Java侧对应

    规定第一个参数必须为 context.Context,最后一个返回值必须为 error

  1. import(
  2. "dubbo.apache.org/dubbo-go/v3/config"
  3. )
  4. var (
  5. userProvider = &pkg.UserProvider{}
  6. )
  7. // UserProvider 客户端存根类
  8. type UserProvider struct {
  9. // dubbo标签,用于适配go侧客户端大写方法名 -> java侧小写方法名,只有 dubbo 协议客户端才需要使用
  10. GetUser func(ctx context.Context, req int32) (*User, error) `dubbo:"getUser"`
  11. }
  12. func init(){
  13. // 注册客户端存根类到框架,实例化客户端接口指针 userProvider
  14. config.SetConsumerService(userProvider)
  15. }
  1. 撰写配置文件: dubbogo.yml
  1. dubbo:
  2. registries:
  3. demoZK: # 定义注册中心ID
  4. protocol: zookeeper
  5. timeout: 3s
  6. address: 127.0.0.1:2181
  7. consumer:
  8. references:
  9. UserProvider: # 存根类名
  10. protocol: dubbo # dubbo 协议,默认 hessian2 序列化方式
  11. interface: org.apache.dubbo.UserProvider # 接口需要与Java侧对应
  12. logger:
  13. zap-config:
  14. level: info # 日志级别

或者使用Triple + Hessian2 序列化请求Server。本例子如果跟Java Server互通则不能用Triple。

  1. dubbo:
  2. registries:
  3. demoZK:
  4. protocol: zookeeper
  5. timeout: 3s
  6. address: 127.0.0.1:2181
  7. consumer:
  8. references:
  9. UserProvider:
  10. protocol: tri # triple 协议
  11. serialization: hessian2 # 序列化方式 hessian2,triple 协议默认为 pb 序列化,不配置会报错
  12. interface: org.apache.dubbo.UserProvider
  13. logger:
  14. zap-config:
  15. level: info
  1. 撰写 main.go 文件,发起调用
  1. func main(){
  2. config.Load()
  3. var i int32 = 1
  4. user, err := userProvider.GetUser2(context.TODO(), i)
  5. if err != nil {
  6. panic(err)
  7. }
  8. logger.Infof("response result: %v", user)
  9. }
  1. 可查看到调用成功的日志,符合预期
  • go-client
  1. response result: User{ID:1, Name:userCode get, Age:48, Time:2021-10-21 20:25:26.009 +0800 CST}

2.2 Java-Client -> Go-Server

Go-Server 启动

  1. 定义配置文件
  1. dubbo:
  2. registries:
  3. demoZK:
  4. protocol: zookeeper
  5. address: 127.0.0.1:2181
  6. protocols:
  7. dubbo:
  8. name: dubbo
  9. port: 20000
  10. provider:
  11. services:
  12. UserProvider:
  13. interface: org.apache.dubbo.UserProvider
  14. logger:
  15. zap-config:
  16. level: info
  1. 引入传输结构,定义服务以及方法名映射
  1. type UserProvider struct {
  2. }
  3. func (u *UserProvider) GetUser(ctx context.Context, req int32) (*User, error) {
  4. var err error
  5. logger.Infof("req:%#v", req)
  6. user := &User{}
  7. user.ID = strconv.Itoa(int(req))
  8. return user, err
  9. }
  10. // MethodMapper 定义方法名映射,从 Go 的方法名映射到 Java 小写方法名,只有 dubbo 协议服务接口才需要使用
  11. func (s *UserProvider) MethodMapper() map[string]string {
  12. return map[string]string{
  13. "GetUser": "getUser",
  14. }
  15. }
  16. func init(){
  17. config.SetProviderService(&pkg.UserProvider{})
  18. }
  1. 启动服务
  1. // export DUBBO_GO_CONFIG_PATH=dubbogo.yml
  2. func main() {
  3. if err := config.Load(); err != nil {
  4. panic(err)
  5. }
  6. select {}
  7. }

Java-Client 启动

  1. Java 客户端 Spring 配置

    resources/META-INF.spring/dubbo.consumer.xml

    ``` <?xml version=”1.0” encoding=”UTF-8”?>

  1. <!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
  2. <dubbo:application name="user-info-client" />
  3. <!-- 连接到哪个本地注册中心 -->
  4. <dubbo:registry id="dubbogo" address="zookeeper://127.0.0.1:2181" />
  5. <!-- dubbo.registry.address from dubbo.properties -->
  6. <!-- dubbo:registry address="${dubbo.registry.address}" / -->
  7. <!-- 用dubbo协议在20880端口暴露服务 -->
  8. <dubbo:protocol id="dubbo" name="dubbo" />
  9. <!-- 声明需要使用的服务接口 -->
  10. <dubbo:reference registry="dubbogo" check="false" id="userProvider" protocol="dubbo" interface="org.apache.dubbo.UserProvider">
  11. <!--<dubbo:parameter key="heartbeat" value="10000"/ -->
  12. </dubbo:reference>
  13. <dubbo:reference registry="dubbogo" check="false" id="userProvider1" protocol="dubbo" version="2.0" interface="org.apache.dubbo.UserProvider">
  14. </dubbo:reference>
  15. <dubbo:reference registry="dubbogo" check="false" id="userProvider2" protocol="dubbo" version="2.0" group="as" interface="org.apache.dubbo.UserProvider">
  16. </dubbo:reference>
  17. </beans>
  18. ```
  1. 发起调用
  1. public class Consumer {
  2. // Define a private variable (Required in Spring)
  3. private static UserProvider userProvider;
  4. public static void main(String[] args) throws Exception {
  5. ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo.consumer.xml"});
  6. userProvider = (UserProvider)context.getBean("userProvider");
  7. testGetUser();
  8. }
  9. private static void testGetUser() throws Exception {
  10. User user = userProvider.getUser(1);
  11. System.out.println(user.getId());
  12. }
  13. }

最后修改 December 16, 2022: Fix check (#1736) (97972c1)