You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GRPC Java客户端接收google.protobuf.Any时type_url与value为空问题

问题描述

GRPC跨语言调用场景:服务端用Golang实现,客户端为Java实现。服务端定义的Response包含google.protobuf.Any类型的data字段,但Java客户端接收响应后发现该Any对象的type_url和value均为空,有效数据全部存储在unknownFields字段中。已确认服务端逻辑无问题,相关代码及Proto定义如下:

Java客户端代码

package com.yy.shophub.gateway.grpc.proxy.server.examples;

import com.google.protobuf.InvalidProtocolBufferException;
import com.yy.shophub.gateway.grpc.proto.*;
import io.grpc.Channel;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;

import java.util.concurrent.TimeUnit;

public class ProductClient {
    private final ProductGrpc.ProductBlockingStub blockingStub;

    /**
     * Construct client for accessing HelloWorld server using the existing channel.
     */
    public ProductClient(Channel channel) {
        // 'channel' here is a Channel, not a ManagedChannel, so it is not this code's responsibility to
        // shut it down.

        // Passing Channels to code makes code easier to test and makes it easier to reuse Channels.
        blockingStub = ProductGrpc.newBlockingStub(channel);
    }

    /**
     * Say hello to server.
     */
    public void getProduct() throws InvalidProtocolBufferException {
        BasicRpc.AuthID authID = BasicRpc.AuthID.newBuilder()
                .setChannel("SHIPIFY")
                .setTenant("T001")
                .setUniqueKey("6141c23b3ee6320023d6e674")
                .build();

        ProductRpc.ProductGetRequest grpcRequest = ProductRpc.ProductGetRequest.newBuilder()
                .setAuthID(authID)
                .setProductId("644784a6aaa03b0011fe4a89")
                .setProductType(0)
                .build();
        R.Response response;
        try {
            response = blockingStub.getProduct(grpcRequest);
        } catch (StatusRuntimeException e) {
            System.err.println(e);
            return;
        }

        if (response.getData().is(ProductRpc.ProductGetResponse.class)) {
            ProductRpc.ProductGetResponse unpack = response.getData().unpack(ProductRpc.ProductGetResponse.class);
            System.out.println(unpack);
        }
    }

    public static void main(String[] args) throws Exception {
        String target = "127.0.0.1:20881";

        ManagedChannel channel = ManagedChannelBuilder.forTarget(target)
                // Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
                // needing certificates.
                .usePlaintext()
                .build();
        try {
            ProductClient client</think_never_used_51bce0c785ca2f68081bfa7d91973934></think_never_used_51bce0c785ca2f68081bfa7d91973934>抱歉,刚才的内容没写完,现在补全完整的回答:

---

## 问题描述
GRPC跨语言调用场景:服务端用Golang实现,客户端为Java实现。服务端定义的`Response`包含`google.protobuf.Any`类型的`data`字段,但Java客户端接收响应后发现该`Any`对象的`type_url`和`value`均为空,有效数据全部存储在`unknownFields`字段中。已确认服务端逻辑无问题,相关代码及Proto定义如下:

### Java客户端代码
```java
package com.yy.shophub.gateway.grpc.proxy.server.examples;

import com.google.protobuf.InvalidProtocolBufferException;
import com.yy.shophub.gateway.grpc.proto.*;
import io.grpc.Channel;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;

import java.util.concurrent.TimeUnit;

public class ProductClient {
    private final ProductGrpc.ProductBlockingStub blockingStub;

    /**
     * Construct client for accessing HelloWorld server using the existing channel.
     */
    public ProductClient(Channel channel) {
        // 'channel' here is a Channel, not a ManagedChannel, so it is not this code's responsibility to
        // shut it down.

        // Passing Channels to code makes code easier to test and makes it easier to reuse Channels.
        blockingStub = ProductGrpc.newBlockingStub(channel);
    }

    /**
     * Say hello to server.
     */
    public void getProduct() throws InvalidProtocolBufferException {
        BasicRpc.AuthID authID = BasicRpc.AuthID.newBuilder()
                .setChannel("SHIPIFY")
                .setTenant("T001")
                .setUniqueKey("6141c23b3ee6320023d6e674")
                .build();

        ProductRpc.ProductGetRequest grpcRequest = ProductRpc.ProductGetRequest.newBuilder()
                .setAuthID(authID)
                .setProductId("644784a6aaa03b0011fe4a89")
                .setProductType(0)
                .build();
        R.Response response;
        try {
            response = blockingStub.getProduct(grpcRequest);
        } catch (StatusRuntimeException e) {
            System.err.println(e);
            return;
        }

        if (response.getData().is(ProductRpc.ProductGetResponse.class)) {
            ProductRpc.ProductGetResponse unpack = response.getData().unpack(ProductRpc.ProductGetResponse.class);
            System.out.println(unpack);
        }
    }

    public static void main(String[] args) throws Exception {
        String target = "127.0.0.1:20881";

        ManagedChannel channel = ManagedChannelBuilder.forTarget(target)
                // Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
                // needing certificates.
                .usePlaintext()
                .build();
        try {
            ProductClient client = new ProductClient(channel);
            client.getProduct();
        } finally {
            channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
        }
    }
}

服务端Response的Proto定义

syntax = "proto3";
import "google/protobuf/any.proto";
package r;

message Response {
  string code = 1;
  string msg = 2;
  google.protobuf.Any data = 3;
}

排查分析与解决方法

核心原因

  1. 类型URL不匹配:Golang服务端打包Any时生成的type_url前缀与Java客户端的Proto包名不一致,Java解析器无法识别该类型,只能将数据归入unknownFields。
  2. Java端未注册消息类型:Protobuf的Any类型需要提前注册对应的消息类型,否则解析器无法映射目标类型,导致数据无法正常解析。
  3. 版本兼容性问题:Golang与Java端使用的Protobuf版本差异过大,导致Any的序列化/反序列化逻辑不兼容。

具体解决步骤

  1. 统一类型URL前缀
    在Golang服务端打包Any对象时,明确指定与Java客户端匹配的type_url,确保类型标识一致:

    import "google.golang.org/protobuf/types/known/anypb"
    
    // 对应Java端消息类型全名为com.yy.shophub.gateway.grpc.proto.ProductRpc.ProductGetResponse
    anyData, err := anypb.New(productGetResponse)
    if err != nil {
        // 处理错误
    }
    // 手动设置type_url为Java可识别的格式
    anyData.TypeUrl = "type.googleapis.com/com.yy.shophub.gateway.grpc.proto.ProductRpc.ProductGetResponse"
    
  2. Java端注册消息类型
    在Java客户端解析Any字段前,先注册对应的消息类型:

    public void getProduct() throws InvalidProtocolBufferException {
        // 提前注册目标消息类型
        Any.register(ProductRpc.ProductGetResponse.class);
        
        // 原有请求构建与调用逻辑...
    }
    

    或者在构建Stub时指定扩展注册表:

    public ProductClient(Channel channel) {
        ExtensionRegistry registry = ExtensionRegistry.newInstance();
        registry.add(ProductRpc.ProductGetResponse.getDefaultInstance());
        blockingStub = ProductGrpc.newBlockingStub(channel).withExtensionRegistry(registry);
    }
    
  3. 对齐Protobuf版本
    检查并确保Golang端的google.golang.org/protobuf版本与Java端的com.google.protobuf:protobuf-java版本一致(例如统一使用3.25.3版本),避免版本差异导致的序列化逻辑冲突。


内容的提问来源于stack exchange,提问作者Hxd Legend

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 15:57:00