Cross-language interoperability with Java applications

Preparation

environment

JDK 8, Golang >= 1.15, Dubbo 3.0.2, zookeeper enabled,

Go-Java Interoperability Prerequisites

  • The transfer structure defined by Go/Java is consistent

    • PB serialization

    proto for Go

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

    proto for Java

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

    POJO for Go, please refer to Dubbogo Hessian serialization support document

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

    POJOs for Java

    package org.apache.dubbo
    
    public class User {
      private String id;
      private String name;
      private int age;
    }
    
  • Java requires the same method signature as Go

    E.g:

    Java Interface

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

    Go client (automatically generated by protoc-gen-go-triple based on proto files)

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

    Go server (defined by the developer)

    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 methods need to comply with Dubbogo 3.0 User Service Interface Definition Specification

  • Java’s triplet is consistent with the interface configured by Go service/reference

    The triplet is configured at the interface level: interface, group, version. **It should be noted that the concept of group and version is the group and vesion of the dubbo interface, which are configured in the properties file of spring cloud when starting the dubbo-java service, not the version that mvn depends on in pom.xml. ** group and version are empty by default, in the dubbo-go framework, you can specify group and version in the corresponding position of service/reference.

    E.g:

    Java interface full name: com.apache.dubbo.sample.basic.IGreeter, interface version is v1.0.1, group is

    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. Intercommunication based on Triple protocol (PB serialization)

Reference dubbo-go-samples/helloworld

1.1 Go-Client -> Java-Server

Java-Server start

  1. Define Java PB file, please refer to Dubbo Quick Start
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;
}

The interface description file defines the generated Java class org.apache.dubbo.sample.hello.Helloworld, and the transmission structure HelloRequest and User class included in the class.

  1. Define the service interface:

    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. Implement the service interface:

    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. Use the Dubbo3 framework to start the service

    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();
    }
}

Start the service, you can see the following log output, which means that the Java Triple Server started successfully

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

Go-Client start

For the Dubbo service that has been started, if you need to develop its corresponding Go-client, you need to perform the following steps:

  1. Write a proto file adapted to Java

    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. Use protoc-gen-triple to generate interface files
protoc -I .samples_api.proto --triple_out=plugins=triple:.
  1. Write configuration file: 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. Write the main.go file and initiate the call
// 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. You can view the log of the successful call
  • 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 start

  1. Define the configuration file
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. Introduce the transport structure and define the service
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. Start the service
// export DUBBO_GO_CONFIG_PATH=dubbogo.yml
func main() {
config. SetProviderService(&GreeterProvider{})
if err := config.Load(); err != nil {
panic(err)
}
select {}
}

Java-Client start

  1. Proto file writing and interface generation refer to the introduction of the above java-server

  2. Start 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. Intercommunication based on Dubbo protocol (Hessian2 serialization)

Reference dubbo-go-samples/rpc/dubbo

2.1 Go-Client -> Java-Server

Java-Server start

  1. Define the Java interface, parameters and return values, please refer to Dubbo Quick Start
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. Implement the service interface:

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. Start with 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. Configure Dubbo parameters through Spring

    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>

Start the Provider class, and you can see the following log output, which means that the Dubbo Server has started successfully

[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 start

For the Dubbo service that has been started, if you need to develop its corresponding Go-client, you need to perform the following steps:

  1. Write the POJO class User adapted to Java
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. Write a client stub class that is consistent with the Java side, and its interface method needs to correspond to the Java side

    It is stipulated that the first parameter must be context.Context, and the last return value must be 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. Write configuration file: 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

Or use Triple + Hessian2 to serialize the request Server. If this example communicates with Java Server, Triple cannot be used.

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. Write the main.go file and initiate the call
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. You can view the log of the successful call, which meets expectations
  • 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 start

  1. Define the configuration file
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. Introduce transport structure, define service and method name mapping
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. Start the service
// export DUBBO_GO_CONFIG_PATH=dubbogo.yml
func main() {
if err := config.Load(); err != nil {
panic(err)
}
select {}
}

Java-Client start

  1. Java client Spring configuration

    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. Initiate the call

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());
    }
}

Last modified January 2, 2023: Enhance Dubbogo docs (#1800) (71c8e72274)