与 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 服务器启动
- 定义 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 类。
定义服务接口
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);
}
实现服务接口
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;
}
}
使用 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 客户端,则需要执行以下步骤
编写适配 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;
}
- 使用 protoc-gen-triple 生成接口文件
protoc -I .samples_api.proto --triple_out=plugins=triple:.
- 编写配置文件: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
- 编写 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)
}
- 您可以查看成功调用的日志
- Go 客户端
cmd/client.go:53 client response result: name:"hello laurence" id:"12345" age:18
-Java 服务器
receiver: name: "laurence"
1.2 Java 客户端 -> Go 服务器
Go 服务器启动
- 定义配置文件
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
- 引入传输结构并定义服务
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
}
- 启动服务
// export DUBBO_GO_CONFIG_PATH=dubbogo.yml
func main() {
config. SetProviderService(&GreeterProvider{})
if err := config.Load(); err != nil {
panic(err)
}
select {}
}
Java 客户端启动
Proto 文件编写和接口生成参考上述 Java 服务器的介绍
启动消费者
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 序列化)
2.1 Go 客户端 -> Java 服务器
Java 服务器启动
- 定义 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();
/* ... */
}
- 实现服务接口
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);
}
}
- 使用 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();
// }
}
通过 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 客户端,则需要执行以下步骤
- 编写适配 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
}
编写与 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)
}
- 编写配置文件: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
- 编写 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)
}
- 您可以查看成功调用的日志,符合预期
- 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 服务器启动
- 定义配置文件
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
- 引入传输结构,定义服务和方法名映射
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{})
}
- 启动服务
// export DUBBO_GO_CONFIG_PATH=dubbogo.yml
func main() {
if err := config.Load(); err != nil {
panic(err)
}
select {}
}
Java 客户端启动
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>
发起调用
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());
}
}