與 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 的 properties 檔案中配置的,而不是 pom.xml 中 mvn 依賴的版本。** 根據預設,群組和版本為空,在 dubbo-go 框架中,您可以在服務/參考的對應位置指定群組和版本。

    例如

    Java 介面完整名稱:com.apache.dubbo.sample.basic.IGreeter,介面版本為 v1.0.1,群組為

    Go-client

    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-server

    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-Client -> Java-Server

Java-Server 啟動

  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-Client 啟動

對於已啟動的 Dubbo 服務,如果您需要開發其對應的 Go-client,則需要執行以下步驟

  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-client
cmd/client.go:53 client response result: name:"hello laurence" id:"12345" age:18

-java-server

receiver: name: "laurence"

1.2 Java-Client -> Go-Server

Go-Server 啟動

  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-Client 啟動

  1. Proto 檔案編寫和介面生成請參考上述 java-server 的介紹

  2. 啟動 Consumer

    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-Client -> Java-Server

Java-Server 啟動

  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

       http://www.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-Client 啟動

對於已啟動的 Dubbo 服務,如果您需要開發其對應的 Go-client,則需要執行以下步驟

  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 端一致的 client stub 類別,其介面方法需與 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-client
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. 定義設定檔
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-Client 啟動

  1. Java client 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
    
           http://www.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)