Java 转 go 学习 - dubbo-go(2)

文章目录

  • [1. dubbo 协议](#1. dubbo 协议)
    • [1.1 证书](#1.1 证书)
    • [1.2 配置文件](#1.2 配置文件)
    • [1.3 接口定义](#1.3 接口定义)
    • [1.4 服务端](#1.4 服务端)
    • [1.5 客户端启动](#1.5 客户端启动)
    • [1.6 示例](#1.6 示例)
  • [2. go 调用 java dubbo 接口](#2. go 调用 java dubbo 接口)
    • [2.1 Java Dubbo 接口提供](#2.1 Java Dubbo 接口提供)
      • [2.1.1 bean](#2.1.1 bean)
      • [2.1.2 接口](#2.1.2 接口)
      • [2.1.3 配置文件](#2.1.3 配置文件)
    • [2.2 go 代码调用](#2.2 go 代码调用)
      • [2.2.1 接口提供](#2.2.1 接口提供)
      • [2.2.2 配置文件](#2.2.2 配置文件)
      • [2.2.3 consumer main 方法](#2.2.3 consumer main 方法)
      • [2.2.4 启动输出](#2.2.4 启动输出)
  • [3. 小结](#3. 小结)

本系列文章:


1. dubbo 协议

官方地址:Dubbo-go 协议快速开始,有需要可以直接看官方给的示例,还是比较详细的,我这里就是用 codex 生成的一个示例。

1.1 证书

首先就是协议证书,目前这个版本是 v3.3.1,但是这个版本好像都有个 bug,使用默认的协议就一定要配置 tls,如果不配置就会报错,具体看这个链接。

看评论在 3.3.2 版本已经修复了,但是这个版本目前还引用不了,所以本地用一个脚本去生成 tls 证书。

go 复制代码
package main

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"fmt"
	"log"
	"math/big"
	"net"
	"os"
	"path/filepath"
	"time"
)

func main() {
	baseDir := filepath.Join("certs")
	must(os.MkdirAll(baseDir, 0o755))

	caKey, err := rsa.GenerateKey(rand.Reader, 2048)
	must(err)
	caTemplate := &x509.Certificate{
		SerialNumber:          serial(),
		Subject:               pkix.Name{CommonName: "dubbo-4 local ca"},
		NotBefore:             time.Now().Add(-time.Hour),
		NotAfter:              time.Now().AddDate(5, 0, 0),
		KeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
		BasicConstraintsValid: true,
		IsCA:                  true,
	}
	caDER, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, &caKey.PublicKey, caKey)
	must(err)
	writeCert(filepath.Join(baseDir, "ca.crt"), caDER)
	writeKey(filepath.Join(baseDir, "ca.key"), caKey)

	serverKey, serverCert := issueLeaf(caTemplate, caKey, "dubbo-4-server", []string{"localhost"}, []net.IP{net.ParseIP("127.0.0.1")}, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth})
	writeKey(filepath.Join(baseDir, "server.key"), serverKey)
	writeCert(filepath.Join(baseDir, "server.crt"), serverCert)

	clientKey, clientCert := issueLeaf(caTemplate, caKey, "dubbo-4-client", nil, nil, []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth})
	writeKey(filepath.Join(baseDir, "client.key"), clientKey)
	writeCert(filepath.Join(baseDir, "client.crt"), clientCert)

	fmt.Println("generated certificates:")
	fmt.Println(filepath.Join(baseDir, "ca.crt"))
	fmt.Println(filepath.Join(baseDir, "ca.key"))
	fmt.Println(filepath.Join(baseDir, "server.crt"))
	fmt.Println(filepath.Join(baseDir, "server.key"))
	fmt.Println(filepath.Join(baseDir, "client.crt"))
	fmt.Println(filepath.Join(baseDir, "client.key"))
}

func issueLeaf(ca *x509.Certificate, caKey *rsa.PrivateKey, cn string, dns []string, ips []net.IP, usage []x509.ExtKeyUsage) (*rsa.PrivateKey, []byte) {
	key, err := rsa.GenerateKey(rand.Reader, 2048)
	must(err)
	template := &x509.Certificate{
		SerialNumber: serial(),
		Subject:      pkix.Name{CommonName: cn},
		NotBefore:    time.Now().Add(-time.Hour),
		NotAfter:     time.Now().AddDate(2, 0, 0),
		KeyUsage:     x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
		ExtKeyUsage:  usage,
		DNSNames:     dns,
		IPAddresses:  ips,
	}
	der, err := x509.CreateCertificate(rand.Reader, template, ca, &key.PublicKey, caKey)
	must(err)
	return key, der
}

func writeCert(path string, der []byte) {
	file, err := os.Create(path)
	must(err)
	defer file.Close()
	must(pem.Encode(file, &pem.Block{Type: "CERTIFICATE", Bytes: der}))
}

func writeKey(path string, key *rsa.PrivateKey) {
	file, err := os.Create(path)
	must(err)
	defer file.Close()
	must(pem.Encode(file, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}))
}

func serial() *big.Int {
	n, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
	must(err)
	return n
}

func must(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

1.2 配置文件

接下来是 provider.yaml,生产者的配置信息。

go 复制代码
dubbo:
  application:
    name: dubbo-4-provider
    module: dubbo-protocol-provider
    version: 1.0.0

  # 这里配置 provider 自己的 TLS 证书
  # dubbo-go/getty 这一版会从全局 tls_config 里读取证书文件路径
  tls_config:
    # 服务端自己的证书链文件
    tls-cert-file: certs/server.crt
    # 服务端自己的私钥文件
    tls-key-file: certs/server.key
    # 用来校验对端客户端证书的 CA
    ca-cert-file: certs/ca.crt
    # 证书里的服务名,这里本地自签用 localhost
    tls-server-name: localhost

  protocols:
    dubbo:
      name: dubbo
      ip: 127.0.0.1
      port: 20000

  registries:
    zk:
      protocol: zookeeper
      timeout: 3s
      address: 127.0.0.1:2181

  provider:
    register: true
    registry-ids:
      - zk
    services:
      # 服务
      com.example.dubbo4.HelloService:
        interface: com.example.dubbo4.HelloService
        protocol: dubbo
        group: dubbo-4
        version: 1.0.0
        registry-ids:
          - zk

还有消费者的 consumer.yaml 如下。

go 复制代码
# 这是传统 dubbo 协议的 consumer 配置。
dubbo:
  application:
    name: dubbo-4-consumer
    module: dubbo-protocol-consumer
    version: 1.0.0

  # 这里配置 consumer 侧的 TLS。
  # consumer 要带自己的客户端证书,同时要信任 provider 使用的 CA。
  tls_config:
    tls-cert-file: certs/client.crt
    tls-key-file: certs/client.key
    ca-cert-file: certs/ca.crt
    tls-server-name: localhost

  registries:
    zk:
      protocol: zookeeper
      timeout: 3s
      address: 127.0.0.1:2181

  consumer:
    check: true
    request-timeout: 3s
    registry-ids:
      - zk
    references:
      com.example.dubbo4.HelloService:
        interface: com.example.dubbo4.HelloService
        protocol: dubbo
        group: dubbo-4
        version: 1.0.0
        registry-ids:
          - zk
        timeout: 2s

1.3 接口定义

下面是接口定义的代码。

go 复制代码
package service

import (
	"context"

	hessian "github.com/apache/dubbo-go-hessian2"
)

func init() {
	// 传统 dubbo 协议走 Hessian2, 只要方法里传输自定义结构体,就需要先把 POJO 注册给 hessian 库。
	hessian.RegisterPOJO(&User{})
}

// User 是示例里跨 consumer/provider 传输的自定义结构体。
// 这种 POJO 在 dubbo 协议下需要实现 JavaClassName。
type User struct {
	Name  string `hessian:"name"`
	Age   int32  `hessian:"age"`
	Email string `hessian:"email"`
}

// JavaClassName 返回这个 Go 结构体在 Java 世界里的类名。
func (u *User) JavaClassName() string {
	return "com.example.dubbo4.User"
}

// HelloService 是 dubbo 协议示例里导出和引用的接口。
// 传统 dubbo-go 这条线不走 proto 生成代码,而是直接靠接口名 + Hessian2 序列化。
type HelloService struct{}

// Reference 返回 consumer 侧要匹配的接口名。
func (s *HelloService) Reference() string {
	return "com.example.dubbo4.HelloService"
}

// JavaClassName 返回服务接口在 Java 侧看到的类名。
func (s *HelloService) JavaClassName() string {
	return "com.example.dubbo4.HelloService"
}

// UpdateUser 演示 POJO 同时作为入参和返回值。
func (s *HelloService) UpdateUser(ctx context.Context, user *User) (*User, error) {
	return &User{
		Name:  user.Name,
		Age:   user.Age + 1,
		Email: "updated+" + user.Email,
	}, nil
}

上面有几个关键的要点:

  1. hessian.RegisterPOJO(&User{}) 注册结构体,因为 dubbo 协议这条线底层用的是 Hessian2,而 Hessian2 在处理 自定义对象 的时候,必须知道这是什么类。当 consumer 把 User 发出去的时候,协议里不只是传字段值,还要带这个对象的类型信息,同理 provider 收到请求之后也不能只拿到一个普通 map 去接收参数,它需要知道应该把数据还原成哪个 Go 结构体。
  2. JavaClassName 配置这个 Go 结构体在 Java 里的类名,如果要调用 java 提供的 dubbo 方法,就需要配置这个。
  3. Reference 方法返回 consumer 侧要匹配的接口名。

1.4 服务端

go 复制代码
package main

import (
	"log"
	"time"

	dubbo "dubbo.apache.org/dubbo-go/v3"
	_ "dubbo.apache.org/dubbo-go/v3/imports"
	"example.com/dubbo-4/service"
)

func main() {
	// 配置化启动时,先把本地 Go 服务实现登记给 dubbo-go。
	dubbo.SetProviderService(&service.HelloService{})

	if err := dubbo.Load(dubbo.WithPath("conf/provider.yaml")); err != nil {
		log.Fatal(err)
	}

	log.Println("dubbo-4 provider started with dubbo protocol")
	for {
		time.Sleep(time.Hour)
	}
}

服务端就很简单了,先把本地 Go 服务实现登记给 dubbo-go,然后解析配置文件即可。

1.5 客户端启动

客户端也是,同理。

go 复制代码
package main

import (
	"context"
	"log"

	dubbo "dubbo.apache.org/dubbo-go/v3"
	_ "dubbo.apache.org/dubbo-go/v3/imports"
	"example.com/dubbo-4/service"
)

func main() {
	svc := new(service.HelloService)
	dubbo.SetConsumerService(svc)

	if err := dubbo.Load(dubbo.WithPath("conf/consumer.yaml")); err != nil {
		log.Fatal(err)
	}

	resp, err := svc.UpdateUser(context.Background(), &service.User{
		Name:  "dubbo-go",
		Age:   20,
		Email: "dubbo-go@example.com",
	})
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("updateUser response: name=%s age=%d email=%s", resp.Name, resp.Age, resp.Email)
}

1.6 示例

启动服务端和客户端,输出分别如下。

2. go 调用 java dubbo 接口

上面的示例都是 go 调用 go 的接口,在 1.3 接口定义我们也看到了通过 JavaClassName 配置 Java 的对象映射,所以这一小节就来看下 go 是如何调用 Java 的 dubbo 接口,首先所用的依赖版本如下。

go 复制代码
require (
	github.com/apache/dubbo-go v1.5.8
	github.com/apache/dubbo-go-hessian2 v1.9.4
)

2.1 Java Dubbo 接口提供

项目结构如下。

2.1.1 bean

首先是几个 bean,Address。

java 复制代码
package com.demo.bean;

import java.io.Serializable;

public class Address implements Serializable {
    private String city;
    private String street;
    private String zipCode;

    // 无参构造(Hessian序列化必须)
    public Address() {}

    // Getter & Setter
    public String getCity() { return city; }
    public void setCity(String city) { this.city = city; }
    public String getStreet() { return street; }
    public void setStreet(String street) { this.street = street; }
    public String getZipCode() { return zipCode; }
    public void setZipCode(String zipCode) { this.zipCode = zipCode; }
}

Order.Java。

java 复制代码
package com.demo.bean;

import java.io.Serializable;
import java.util.List;
import java.util.Map;

public class Order implements Serializable {
    private String orderNo;
    private User buyer;
    private List<OrderItem> items;
    private Map<String, String> tags;

    public Order() {}

    // Getter & Setter
    public String getOrderNo() { return orderNo; }
    public void setOrderNo(String orderNo) { this.orderNo = orderNo; }
    public User getBuyer() { return buyer; }
    public void setBuyer(User buyer) { this.buyer = buyer; }
    public List<OrderItem> getItems() { return items; }
    public void setItems(List<OrderItem> items) { this.items = items; }
    public Map<String, String> getTags() { return tags; }
    public void setTags(Map<String, String> tags) { this.tags = tags; }
}

OrderItem.java

java 复制代码
package com.demo.bean;

import java.io.Serializable;

public class OrderItem implements Serializable {
    private String sku;
    private Integer quantity;
    private Long price;

    public OrderItem() {}

    // Getter & Setter
    public String getSku() { return sku; }
    public void setSku(String sku) { this.sku = sku; }
    public Integer getQuantity() { return quantity; }
    public void setQuantity(Integer quantity) { this.quantity = quantity; }
    public Long getPrice() { return price; }
    public void setPrice(Long price) { this.price = price; }
}

User.java。

go 复制代码
package com.demo.bean;

import java.io.Serializable;

public class User implements Serializable {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    private Address address;

    public User() {}

    // Getter & Setter
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    public Address getAddress() { return address; }
    public void setAddress(Address address) { this.address = address; }
}

2.1.2 接口

然后下面是 service 接口和实现类。

go 复制代码
package com.demo.service;

import com.demo.bean.Order;
import com.demo.bean.User;

import java.util.List;
import java.util.Map;

public interface UserProfileService {
    String echoBasic(long id, String name, boolean active);
    Order mirrorOrder(Order order);
    List<User> mirrorUsers(List<User> users);
    Map<String, String> mirrorAttributes(Map<String, String> attrs);
}
java 复制代码
package com.demo.service.impl;

import com.demo.bean.Order;
import com.demo.bean.User;
import com.demo.service.UserProfileService;
import org.apache.dubbo.config.annotation.DubboService;
import java.util.List;
import java.util.Map;

public class UserProfileServiceImpl implements UserProfileService {

    @Override
    public String echoBasic(long id, String name, boolean active) {
        return "Java Provider Echo: id=" + id + ", name=" + name + ", active=" + active;
    }

    @Override
    public Order mirrorOrder(Order order) {
        return order;
    }

    @Override
    public List<User> mirrorUsers(List<User> users) {
        return users;
    }

    @Override
    public Map<String, String> mirrorAttributes(Map<String, String> attrs) {
        return attrs;
    }
}

2.1.3 配置文件

来看下 dubbo 的配置文件,内容如下。

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://dubbo.apache.org/schema/dubbo
       http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <!-- 1. Dubbo 应用配置(和原配置一致) -->
    <dubbo:application name="dubbo5-provider"/>

    <!-- 2. Dubbo 协议配置(dubbo 协议 + 20880端口) -->
    <dubbo:protocol name="dubbo" port="20880"/>

    <!-- 3. 注册中心配置(Zookeeper 地址 + 超时时间) -->
    <dubbo:registry
            address="zookeeper://127.0.0.1:2181"
            timeout="30000"/>

    <!-- 4. 暴露 Dubbo 服务(核心!和 Go 消费者完全对齐) -->
    <!-- interface:Java接口全类名 | ref:服务实现类Bean | group/version:严格匹配Go端 -->
    <dubbo:service
            interface="com.demo.service.UserProfileService"
            ref="userProfileServiceImpl"
            group="dubbo-5"
            version="1.0.0"
            timeout="5000"/>

    <!-- 5. 注册服务实现类(替换原注解扫描) -->
    <bean id="userProfileServiceImpl" class="com.demo.service.impl.UserProfileServiceImpl"/>

</beans>

整体来说上面就是很常规的 dubbo 注册,下面来看下 go 的代码。

2.2 go 代码调用

2.2.1 接口提供

go 复制代码
package service

import (
	"context"

	hessian "github.com/apache/dubbo-go-hessian2"
	_ "github.com/apache/dubbo-go/cluster/cluster_impl"
	_ "github.com/apache/dubbo-go/cluster/loadbalance"
	_ "github.com/apache/dubbo-go/common/proxy/proxy_factory"
	"github.com/apache/dubbo-go/config"
	_ "github.com/apache/dubbo-go/filter/filter_impl"
	_ "github.com/apache/dubbo-go/registry/protocol"
	_ "github.com/apache/dubbo-go/registry/zookeeper"

	_ "github.com/apache/dubbo-go/protocol/dubbo"
)

var userProfile *UserProfileService

func init() {
	config.Load()

	svc := new(UserProfileService)
	config.SetConsumerService(svc)

	// dubbo 协议下只要跨进程传自定义结构体,就要先注册成 Hessian POJO。
	hessian.RegisterPOJO(&Address{})
	hessian.RegisterPOJO(&User{})
	hessian.RegisterPOJO(&OrderItem{})
	hessian.RegisterPOJO(&Order{})

	userProfile = svc
}

// Address 对应 Java 侧的 com.demo.bean.Address
type Address struct {
	City    string `hessian:"city"`
	Street  string `hessian:"street"`
	ZipCode string `hessian:"zipCode"`
}

func (*Address) JavaClassName() string {
	return "com.demo.bean.Address"
}

// User 对应 Java 侧的 com.demo.bean.User
type User struct {
	ID      int64    `hessian:"id"`
	Name    string   `hessian:"name"`
	Age     int32    `hessian:"age"`
	Email   string   `hessian:"email"`
	Address *Address `hessian:"address"`
}

func (*User) JavaClassName() string {
	return "com.demo.bean.User"
}

// OrderItem 对应 Java 侧的 com.demo.bean.OrderItem
type OrderItem struct {
	SKU      string `hessian:"sku"`
	Quantity int32  `hessian:"quantity"`
	Price    int64  `hessian:"price"`
}

func (*OrderItem) JavaClassName() string {
	return "com.demo.bean.OrderItem"
}

// Order 对应 Java 侧的 com.example.dubbo5.api.Order。
// 它内部再嵌套 User、Address、List、Map,专门用来演示复杂 POJO。
type Order struct {
	OrderNo string            `hessian:"orderNo"`
	Buyer   *User             `hessian:"buyer"`
	Items   []*OrderItem      `hessian:"items"`
	Tags    map[string]string `hessian:"tags"`
}

func (*Order) JavaClassName() string {
	return "com.demo.bean.Order"
}

// UserProfileService 是 consumer 侧的 dubbo stub。
// 这里不写业务实现,而是让 dubbo-go 在启动时把这些函数字段注入成远程调用代理。
type UserProfileService struct {
	// 基本数据类型入参。
	EchoBasic func(ctx context.Context, id int64, name string, active bool) (string, error) `dubbo:"echoBasic"`
	// 嵌套 struct 入参。
	MirrorOrder func(ctx context.Context, order *Order) (*Order, error) `dubbo:"mirrorOrder"`
	// List 入参。
	MirrorUsers func(ctx context.Context, users []*User) (interface{}, error) `dubbo:"mirrorUsers"`
	// Map 入参。
	MirrorAttributes func(ctx context.Context, attrs map[string]string) (map[interface{}]interface{}, error) `dubbo:"mirrorAttributes"`
}

func (u *UserProfileService) Reference() string {
	return "UserProfileService"
}

func GetUserProfileService() *UserProfileService {
	return userProfile
}

上面就是提供接口的所有代码,下面来看下里面的一些核心逻辑。

  • config.Load() 加载所有配置文件,这个版本的 dubbo-go 配置文件要通过环境变量注入,就是下面的参数:CONF_CONSUMER_FILE_PATH=conf/dubbogo.yml;CONF_PROVIDER_FILE_PATH=conf/dubbogo.yml,指定消费者和生产者配置文件路径。
  • 参数类型,如果 Java 用的是基本数据类型,Go 里面也要用基本数据类型来对应,别用包装类,上面的 EchoBasic 方法,Java 侧一开始我用的包装类,然后 Go 用基本数据类型传参和用指针传参都提示没办法找到 provider,没办法只能改成基本数据类型,这才调用成功。
  • 返回值接收 ,注意上面的 MirrorUsers 和 MirrorAttributes,返回值接收全部用 interface{},集合类型的返回结果不能写成具体类型,否则会报错,只能用 interface{} 接收然后自己转,可能这个是框架的问题,不能自己转?反正我没找到办法处理这个问题,只能自己手动转。

2.2.2 配置文件

go 复制代码
# dubbo client yaml configure file

check: false
# client
request_timeout: "3s"
# connect timeout
connect_timeout: "3s"

# application config
application:
  name: "dubbo-5-consumer"

# registry config
registries:
  "demoZk":
    protocol: "zookeeper"
    timeout: "3s"
    address: "127.0.0.1:2181"

# reference config
references:
  "UserProfileService":
    registry: "demoZk"
    protocol: "dubbo"
    interface: "com.demo.service.UserProfileService"
    cluster: "failover"
    group: "dubbo-5"
    version: "1.0.0"
    methods:
      - name: "echoBasic"
        retries: 3
      - name: "mirrorOrder"
        retries: 3
      - name: "mirrorUsers"
        retries: 3
      - name: "mirrorAttributes"
        retries: 3

# protocol config
protocol_conf:
  dubbo:
    reconnect_interval: 0
    connection_number: 1
    heartbeat_period: "5s"
    session_timeout: "180s"
    pool_size: 64
    pool_ttl: 600
    getty_session_param:
      compress_encoding: false
      tcp_no_delay: true
      tcp_keep_alive: true
      keep_alive_period: "120s"
      tcp_r_buf_size: 262144
      tcp_w_buf_size: 65536
      pkg_rq_size: 1024
      pkg_wq_size: 512
      tcp_read_timeout: "1s"
      tcp_write_timeout: "5s"
      wait_timeout: "1s"
      max_msg_len: 1024000
      session_name: "client"

1.x 版本的写法和 3.x 版本的写法不一样,有很大不同,上面是 1.x 的配置文件写法。

2.2.3 consumer main 方法

最后来看下 consumer 的 main 方法。

go 复制代码
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"

	"example.com/dubbo-5/service"
	_ "github.com/apache/dubbo-go/cluster/cluster_impl"
	_ "github.com/apache/dubbo-go/cluster/loadbalance"
	_ "github.com/apache/dubbo-go/common/proxy/proxy_factory"
	"github.com/apache/dubbo-go/config"
	_ "github.com/apache/dubbo-go/filter/filter_impl"
	_ "github.com/apache/dubbo-go/registry/protocol"
	_ "github.com/apache/dubbo-go/registry/zookeeper"

	_ "github.com/apache/dubbo-go/protocol/dubbo"
)

func main() {
	svc := service.GetUserProfileService()

	config.Load()

	ctx := context.Background()

	basicResp, err := svc.EchoBasic(ctx, 1001, "jiang", true)
	if err != nil {
		log.Fatalf("echoBasic error: %v", err)
	}
	log.Printf("echoBasic response: %s", basicResp)

	orderResp, err := svc.MirrorOrder(ctx, sampleOrder())
	if err != nil {
		log.Fatalf("mirrorOrder error: %v", err)
	}
	log.Printf("mirrorOrder response:\n%s", pretty(orderResp))

	usersResp, err := svc.MirrorUsers(ctx, sampleUsers())
	if err != nil {
		log.Fatalf("mirrorUsers error: %v", err)
	}
	res := usersResp.([]interface{})
	for _, item := range res {
		user := item.(*service.User)
		fmt.Printf("%v\n", user)
	}

	attrsResp, err := svc.MirrorAttributes(ctx, map[string]string{
		"source": "go-consumer",
		"scene":  "dubbo-5",
	})
	log.Printf("mirrorAttributes response:\n%v", attrsResp)
}

func sampleOrder() *service.Order {
	return &service.Order{
		OrderNo: "DUBBO5-10001",
		Buyer: &service.User{
			ID:    1,
			Name:  "jiang",
			Age:   20,
			Email: "jiang@example.com",
			Address: &service.Address{
				City:    "Shanghai",
				Street:  "Pudong Avenue",
				ZipCode: "200120",
			},
		},
		Items: []*service.OrderItem{
			{SKU: "book-01", Quantity: 2, Price: 99},
			{SKU: "pen-01", Quantity: 3, Price: 15},
		},
		Tags: map[string]string{
			"channel": "web",
			"level":   "vip",
		},
	}
}

func sampleUsers() []*service.User {
	return []*service.User{
		{
			ID:    1,
			Name:  "alice",
			Age:   18,
			Email: "alice@example.com",
			Address: &service.Address{
				City:    "Beijing",
				Street:  "Chaoyang Road",
				ZipCode: "100020",
			},
		},
		{
			ID:    2,
			Name:  "bob",
			Age:   22,
			Email: "bob@example.com",
			Address: &service.Address{
				City:    "Shenzhen",
				Street:  "Nanshan Road",
				ZipCode: "518000",
			},
		},
	}
}

func pretty(v any) string {
	data, err := json.MarshalIndent(v, "", "  ")
	if err != nil {
		return "<marshal error>"
	}
	return string(data)
}

2.2.4 启动输出

go 复制代码
2026/05/05 14:42:56 echoBasic response: Java Provider Echo: id=1001, name=jiang, active=true

2026/05/05 14:42:56 mirrorOrder response:
{
  "OrderNo": "DUBBO5-10001",
  "Buyer": {
    "ID": 1,
    "Name": "jiang",
    "Age": 20,
    "Email": "jiang@example.com",
    "Address": {
      "City": "Shanghai",
      "Street": "Pudong Avenue",
      "ZipCode": "200120"
    }
  },
  "Items": [
    {
      "SKU": "book-01",
      "Quantity": 2,
      "Price": 99
    },
    {
      "SKU": "pen-01",
      "Quantity": 3,
      "Price": 15
    }
  ],
  "Tags": {
    "channel": "web",
    "level": "vip"
  }
}

map[scene:dubbo-5 source:go-consumer]
&{1 alice 18 alice@example.com 0xc0002112f0}
&{2 bob 22 bob@example.com 0xc000211320}

可以看到确实调用 Java 接口成功了。

3. 小结

好了,dubbo 相关的内容就先学到这,主要是日常开发的时候用到的主要也是 go 调用 java 代码。

如有错误,欢迎指出!!!

相关推荐
陈年老古董1 小时前
Hive DML 语言学习笔记:数据加载、插入、导出与导入
hive·笔记·学习·mysql
kkkkkkkkkk_Z1 小时前
新手自学嵌入式 | 学习日记:ARM内核启动代码、时钟系统(PLL/Prescaler/PFD)与IMX6ULL时钟配置
arm开发·学习
丁希希哇1 小时前
强化学习与偏好学习基础:PPO,DPO,GRPO
人工智能·学习·机器学习·大语言模型
破壁者-燕2 小时前
MLE 基础学习 Transformer
深度学习·学习·transformer
JWASX2 小时前
【agent 开发】agent 开发学习 - LangChain(1)
python·学习·agent
星恒随风2 小时前
Linux进程基础(二):进程调度、上下文切换、O(1)调度器、环境变量与虚拟地址空间
linux·笔记·学习·状态模式
徐小黑ACG2 小时前
Golang 基础02 控制语句
开发语言·后端·golang
牧羊人.3332 小时前
动手学深度学习 06|学习率调整
人工智能·深度学习·学习
遇印记3 小时前
数据通信初步
运维·服务器·网络·windows·学习