与 Java 应用的跨语言互操作性

准备工作

环境

JDK 8, Golang >= 1.15, Dubbo 3.0.2, 已启用 zookeeper,

Go-Java 互操作性先决条件

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

    • PB 序列化

    Go 的 proto

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

    Java 的 proto

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

    Go 的 POJO,请参考 Dubbogo Hessian 序列化支持文档

    type User struct {
      ID string
      name string
      Age int32
    }
    
    func (u *User) JavaClassName() string {
    return "org.apache.dubbo.User"
    }
    
    func init(){
    hessian.RegisterPOJO(&User{})
    }
    

    Java 的 POJO

    package org.apache.dubbo
    
    public class User {
      private String id;
      private String name;
      private int age;
    }
    
  • Java 需要与 Go 相同的方法签名

    例如

    Java 接口

    public interface IGreeter {
      /**
       * <pre>
       * Sends a greeting
       * </pre>
       */
    User sayHello(HelloRequest request);
    }
    

    Go 客户端(基于 proto 文件由 protoc-gen-go-triple 自动生成)

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

    Go 服务器(由开发者定义)

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

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

  • Java 的 triplet 与 Go 服务/引用配置的接口一致

    triplet 在接口级别配置:接口、组、版本。**需要注意的是,组和版本的概念是 dubbo 接口的组和版本,是在启动 dubbo-java 服务时在 spring cloud 的配置文件中配置的,而不是在 pom.xml 中 mvn 依赖的版本。** 默认情况下,组和版本为空,在 dubbo-go 框架中,您可以在服务/引用的相应位置指定组和版本。

    例如

    Java 接口全名:com.apache.dubbo.sample.basic.IGreeter,接口版本为 v1.0.1,组为

    Go 客户端

    references:
      GreeterClientImpl:
        protocol: tri
        interface: com.apache.dubbo.sample.basic.IGreeter # must be compatible with grpc or dubbo-java
        group: dubbogo # need to correspond to the server, the default is empty
        version: v1.0.1 # needs to correspond to the server, the default is empty
    

    Go 服务器

    services:
      GreeterProvider:
        protocol-ids: tripleProtocol
        interface: com.apache.dubbo.sample.basic.IGreeter # must be compatible with grpc or dubbo-java
        group: dubbogo # need to correspond to the server, the default is empty
        version: v1.0.1 # needs to correspond to the server, the default is empty
    

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

参考 dubbo-go-samples/helloworld

1.1 Go 客户端 -> Java 服务器

Java 服务器启动

  1. 定义 Java PB 文件,请参考 Dubbo 快速入门
syntax = "proto3";

option java_package = "org.apache.dubbo.sample.hello";

package helloworld;

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

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

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

  1. 定义服务接口

    com.apache.dubbo.sample.basic.IGreeter

package com.apache.dubbo.sample.basic;

// Import the class generated according to PB
import org.apache.dubbo.sample.hello.Helloworld.User;
import org.apache.dubbo.sample.hello.Helloworld.HelloRequest;

public interface IGreeter {
    /**
     * <pre>
     * Sends a greeting
     * </pre>
     */
  // define the interface
User sayHello(HelloRequest request);
}
  1. 实现服务接口

    IGreeter1Impl.java

package com.apache.dubbo.sample.basic;

import org.apache.dubbo.sample.hello.Helloworld.User;
import org.apache.dubbo.sample.hello.Helloworld.HelloRequest;

public class IGreeter1Impl implements IGreeter {
    @Override
    public User sayHello(HelloRequest request) {
        System.out.println("receiv: " + request);
        User usr = User. newBuilder()
                .setName("hello " + request.getName())
                .setAge(18)
                .setId("12345").build();
        return usr;
    }
}
  1. 使用 Dubbo3 框架启动服务

    ApiProvider.java

package com.apache.dubbo.sample.basic;

import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ProtocolConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.ServiceConfig;
import java.util.concurrent.CountDownLatch;

public class ApiProvider {
    public static void main(String[] args) throws InterruptedException {
      ServiceConfig<IGreeter> service = new ServiceConfig<>();
      service.setInterface(IGreeter.class);
      service.setRef(new IGreeter1Impl());
      // Use the Triple protocol
      service.setProtocol(new ProtocolConfig(CommonConstants.TRIPLE, 50051));
      service.setApplication(new ApplicationConfig("demo-provider"));
      // Use ZK as the registration center
      service.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
      service. export();
      System.out.println("dubbo service started");
      new CountDownLatch(1). await();
    }
}

启动服务,您可以看到以下日志输出,这意味着 Java Triple 服务器已成功启动

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

Go 客户端启动

对于已启动的 Dubbo 服务,如果需要开发其对应的 Go 客户端,则需要执行以下步骤

  1. 编写适配 Java 的 proto 文件

    samples_api.proto

syntax = "proto3";
package api; // pacakge name is optional

//necessary
option go_package = "./;api";

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (User) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message User {
  string name = 1;
  string id = 2;
  int32 age = 3;
}
  1. 使用 protoc-gen-triple 生成接口文件
protoc -I .samples_api.proto --triple_out=plugins=triple:.
  1. 编写配置文件:dubbogo.yml
dubbo:
  registries:
    demoZK:
      protocol: zookeeper
      address: 127.0.0.1:2181
  consumer:
    references:
      GreeterClientImpl:
        protocol: tri
        interface: com.apache.dubbo.sample.basic.IGreeter # must be compatible with grpc or dubbo-java
  1. 编写 main.go 文件并发起调用
// Import the generated interface structure
var grpcGreeterImpl = new(api. GreeterClientImpl)

// export DUBBO_GO_CONFIG_PATH=dubbogo.yml
func main() {
config. SetConsumerService(grpcGreeterImpl)
if err := config.Load(); err != nil {
panic(err)
}
time. Sleep(3 * time. Second)

logger.Info("start to test dubbo")
req := &api.HelloRequest{
Name: "Laurence",
}
reply, err := grpcGreeterImpl.SayHello(context.Background(), req)
if err != nil {
logger. Error(err)
}
logger.Infof("client response result: %v\n", reply)
}
  1. 您可以查看成功调用的日志
  • Go 客户端
cmd/client.go:53 client response result: name:"hello laurence" id:"12345" age:18

-Java 服务器

receiver: name: "laurence"

1.2 Java 客户端 -> Go 服务器

Go 服务器启动

  1. 定义配置文件
dubbo:
  registries:
    demoZK:
      protocol: zookeeper
      address: 127.0.0.1:2181
  protocols:
    triple:
      name: tri
      port: 20000
  provider:
    services:
      GreeterProvider:
        interface: com.apache.dubbo.sample.basic.IGreeter # must be compatible with grpc or dubbo-java
  1. 引入传输结构并定义服务
type GreeterProvider struct {
api. GreeterProviderBase
}

func (s *GreeterProvider) SayHello(ctx context.Context, in *api.HelloRequest) (*api.User, error) {
logger.Infof("Dubbo3 GreeterProvider get user name = %s\n", in.Name)
return &api.User{Name: "Hello " + in.Name, Id: "12345", Age: 21}, nil
}
  1. 启动服务
// export DUBBO_GO_CONFIG_PATH=dubbogo.yml
func main() {
config. SetProviderService(&GreeterProvider{})
if err := config.Load(); err != nil {
panic(err)
}
select {}
}

Java 客户端启动

  1. Proto 文件编写和接口生成参考上述 Java 服务器的介绍

  2. 启动消费者

    ApiCnosumer.java

public class ApiConsumer {
    public static void main(String[] args) throws InterruptedException, IOException {
        ReferenceConfig<IGreeter> ref = new ReferenceConfig<>();
        ref. setInterface(IGreeter. class);
        ref. setCheck(false);
        ref.setProtocol(CommonConstants.TRIPLE);
        ref. setLazy(true);
        ref. setTimeout(100000);
        ref. setApplication(new ApplicationConfig("demo-consumer"));
        ref.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
        final IGreeter iGreeter = ref. get();

        System.out.println("dubbo ref started");
        Helloworld.HelloRequest req = Helloworld.HelloRequest.newBuilder().setName("laurence").build();
        try {
            final Helloworld. User reply = iGreeter. sayHello(req);
            TimeUnit. SECONDS. sleep(1);
            System.out.println("Reply:" + reply);
        } catch (Throwable t) {
            t. printStackTrace();
        }
        System.in.read();
    }
}

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

参考 dubbo-go-samples/rpc/dubbo

2.1 Go 客户端 -> Java 服务器

Java 服务器启动

  1. 定义 Java 接口、参数和返回值,请参考 Dubbo 快速入门
package org.apache.dubbo;

// The service interface that needs to be exposed
public interface UserProvider {
    User getUser(int usercode);
}
package org.apache.dubbo;

public class User implements Serializable {

    private String id;

    private String name;

    private int age;

    private Date time = new Date();
/* ... */
}
  1. 实现服务接口

UserProviderImpl.java

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

Provider.java

package org.apache.dubbo;

// use when config by API
/*
import java.util.concurrent.CountDownLatch;

import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ProtocolConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.ServiceConfig;
*/
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Provider {
    // main function, config from spring boot
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo.provider.xml"});
        context. start();
        System.in.read(); // press any key to exit
    }

  
// config by API
// public static void startComplexService() throws InterruptedException {
// ServiceConfig<ComplexProvider> service = new ServiceConfig<>();
// service.setInterface(ComplexProvider.class);
// service. setRef(new ComplexProviderImpl());
// service.setProtocol(new ProtocolConfig(CommonConstants.DUBBO_PROTOCOL, 20001));
// service.setApplication(new ApplicationConfig("demo-provider"));
// service.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
// service. export();
// System.out.println("dubbo service started");
// new CountDownLatch(1). await();
// }
}
  1. 通过 Spring 配置 Dubbo 参数

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

<?xml version="1.0" encoding="UTF-8"?>
<!--
  Licensed under the Apache License, Version 2.0 (the "License");
  You may not use this file except in compliance with the License.
  You may obtain a copy of the License at

       https://apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
-->

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

<!-- application name -->
<dubbo:application name="user-info-server"/>
<!-- which local registry to connect to -->
<dubbo:registry id="dubbogo" address="zookeeper://127.0.0.1:2181" />
<!-- Use dubbo protocol to expose services on port 20880 -->
<dubbo:protocol id="dubbo" name="dubbo" host="127.0.0.1" port="20010" />
<!-- Declare the service interface that needs to be exposed -->
<dubbo:service id="aaa" registry="dubbogo" timeout="3000" interface="org.apache.dubbo.UserProvider" ref="demoService"/>
<dubbo:service id="bbb" registry="dubbogo" timeout="3000" interface="org.apache.dubbo.UserProvider" ref="otherService" version="2.0"/>
<dubbo:service id="ccc" registry="dubbogo" timeout="3000" interface="org.apache.dubbo.UserProvider" ref="otherService" group="as" version="2.0"/>

<bean id="demoService" class="org.apache.dubbo.UserProviderImpl" />
<bean id="otherService" class="org.apache.dubbo.UserProviderAnotherImpl"/>

</beans>

启动 Provider 类,您可以看到以下日志输出,这意味着 Dubbo 服务器已成功启动

[DUBBO] Dubbo Bootstrap is ready., dubbo version: 2.7.7, current host: 127.0.0.1
[DUBBO] Dubbo Bootstrap has started., dubbo version: 2.7.7, current host: 127.0.0.1

Go 客户端启动

对于已启动的 Dubbo 服务,如果需要开发其对应的 Go 客户端,则需要执行以下步骤

  1. 编写适配 Java 的 POJO 类 User
import(
  hessian "github.com/apache/dubbo-go-hessian2"
)

// The field needs to correspond to the Java side, with the first letter capitalized
type User struct {
ID string
name string
Age int32
Time time. Time
}


func (u *User) JavaClassName() string {
return "org.apache.dubbo.User" // needs to correspond to the User class name on the Java side
}

func init(){
hessian.RegisterPOJO(&pkg.User{}) // register POJO
}
  1. 编写与 Java 端一致的客户端存根类,其接口方法需要与 Java 端对应

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

import(
"dubbo.apache.org/dubbo-go/v3/config"
)

var (
userProvider = &pkg. UserProvider{}
)

// UserProvider client stub class
type UserProvider struct {
  // The dubbo label is used to adapt the uppercase method name of the go side client -> the lowercase method name of the java side, only the dubbo protocol client needs to use it
GetUser func(ctx context.Context, req int32) (*User, error) `dubbo:"getUser"`
}

func init(){
  // Register the client stub class to the framework, and instantiate the client interface pointer userProvider
config. SetConsumerService(userProvider)
}
  1. 编写配置文件:dubbogo.yml
dubbo:
  registries:
    demoZK: # Define the registration center ID
      protocol: zookeeper
      timeout: 3s
      address: 127.0.0.1:2181
  consumer:
    references:
      UserProvider: # stub class name
        protocol: dubbo # dubbo protocol, default hessian2 serialization method
        interface: org.apache.dubbo.UserProvider # The interface needs to correspond to the Java side
  logger:
    zap-config:
      level: info # log level

或者使用 Triple + Hessian2 序列化请求服务器。如果此示例与 Java 服务器通信,则不能使用 Triple。

dubbo:
  registries:
    demoZK:
      protocol: zookeeper
      timeout: 3s
      address: 127.0.0.1:2181
  consumer:
    references:
      UserProvider:
        protocol: tri # triple protocol
        serialization: hessian2 # serialization method hessian2, triple protocol defaults to pb serialization, if not configured, an error will be reported
        interface: org.apache.dubbo.UserProvider
  logger:
    zap-config:
      level: info
  1. 编写 main.go 文件并发起调用
func main(){
  config. Load()
var i int32 = 1
user, err := userProvider. GetUser2(context. TODO(), i)
if err != nil {
panic(err)
}
logger.Infof("response result: %v", user)
}
  1. 您可以查看成功调用的日志,符合预期
  • Go 客户端
response result: User{ID:1, Name:userCode get, Age:48, Time:2021-10-21 20:25:26.009 +0800 CST}

2.2 Java 客户端 -> Go 服务器

Go 服务器启动

  1. 定义配置文件
dubbo:
  registries:
    demoZK:
      protocol: zookeeper
      address: 127.0.0.1:2181
  protocols:
    dubbo:
      name: dubbo
      port: 20000
  provider:
    services:
      UserProvider:
        interface: org.apache.dubbo.UserProvider
  logger:
    zap-config:
      level: info
  1. 引入传输结构,定义服务和方法名映射
type UserProvider struct {
}

func (u *UserProvider) GetUser(ctx context.Context, req int32) (*User, error) {
var err error
logger.Infof("req:%#v", req)
user := &User{}
user.ID = strconv.Itoa(int(req))
return user, err
}

// MethodMapper defines method name mapping, from Go method name to Java lowercase method name, only dubbo protocol service interface needs to use
func (s *UserProvider) MethodMapper() map[string]string {
return map[string]string{
"GetUser": "getUser",
}
}

func init(){
  config.SetProviderService(&pkg.UserProvider{})
}
  1. 启动服务
// export DUBBO_GO_CONFIG_PATH=dubbogo.yml
func main() {
if err := config.Load(); err != nil {
panic(err)
}
select {}
}

Java 客户端启动

  1. Java 客户端 Spring 配置

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

    <?xml version="1.0" encoding="UTF-8"?>
    <!--
      Licensed under the Apache License, Version 2.0 (the "License");
      You may not use this file except in compliance with the License.
      You may obtain a copy of the License at
    
           https://apache.org/licenses/LICENSE-2.0
    
      Unless required by applicable law or agreed to in writing, software
      distributed under the License is distributed on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      See the License for the specific language governing permissions and
      limitations under the License.
    -->
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
    
    
    <!-- Consumer application name, used to calculate dependencies, not a matching condition, do not be the same as the provider -->
    <dubbo:application name="user-info-client" />
    <!-- which local registry to connect to -->
    <dubbo:registry id="dubbogo" address="zookeeper://127.0.0.1:2181" />
    <!-- dubbo.registry.address from dubbo.properties -->
    <!-- dubbo:registry address="${dubbo.registry.address}" / -->
    
    <!-- Use dubbo protocol to expose services on port 20880 -->
    <dubbo:protocol id="dubbo" name="dubbo" />
    
    <!-- Declare the service interface that needs to be used -->
    <dubbo:reference registry="dubbogo" check="false" id="userProvider" protocol="dubbo" interface="org.apache.dubbo.UserProvider">
    <!--<dubbo:parameter key="heartbeat" value="10000"/ -->
        </dubbo:reference>
    
    <dubbo:reference registry="dubbogo" check="false" id="userProvider1" protocol="dubbo" version="2.0" interface="org.apache.dubbo.UserProvider">
    </dubbo:reference>
    <dubbo:reference registry="dubbogo" check="false" id="userProvider2" protocol="dubbo" version="2.0" group="as" interface="org.apache.dubbo.UserProvider">
    </dubbo:reference>
    </beans>
    
  2. 发起调用

public class Consumer {
    // Define a private variable (Required in Spring)
    private static UserProvider userProvider;

    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo.consumer.xml"});
        userProvider = (UserProvider)context. getBean("userProvider");
        testGetUser();
    }
  
 
    private static void testGetUser() throws Exception {
        User user = userProvider. getUser(1);
        System.out.println(user.getId());
    }
}

最后修改时间:2024 年 1 月 17 日: 修复损坏的链接 (6651e217e73)