【问题标题】:Sending protobuf as JSON in spring-boot在 spring-boot 中将 protobuf 作为 JSON 发送
【发布时间】:2019-02-02 07:27:34
【问题描述】:

我正在使用带有这个具体定义的 protobufs。

message Hash {
    string category = 1;
    repeated KVPair content = 2;
}

message KVPair {
    string key = 1;
    string value = 2;
}

我想用我的 spring-boot 应用程序将此作为 JSON 发送。 我将此包添加到我的 gradle 依赖项中:

compile group: 'com.google.protobuf', name: 'protobuf-java', version: '3.6.1'

当我尝试使用此代码输出哈希生成的对象时:

@RestController
@RequestMapping("/api/crm/")
public class KVController {

    private final KVService kvService;

    public KVController(KVService kvService) {
        this.kvService = kvService;
    }

    @GetMapping("kv/{category}")
    public Hash getHash(@PathVariable String category) {
        Hash hash = kvService.retrieve(category);
        return hash;
    }
}

它抛出这个最终异常:

原因:com.fasterxml.jackson.databind.exc.InvalidDefinitionException:直接自引用导致循环(通过引用链:com.blaazha.crm.proto.Hash["unknownFields"]->com.google. protobuf.UnknownFieldSet["defaultInstanceForType"]) 在 com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77) ~[jackson-databind-2.9.6.jar:2.9.6] 在 com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1191) ~[jackson-databind-2.9.6.jar:2.9.6] 在 com.fasterxml.jackson.databind.ser.BeanPropertyWriter._handleSelfReference(BeanPropertyWriter.java:944) ~[jackson-databind-2.9.6.jar:2.9.6] 在 com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:721) ~[jackson-databind-2.9.6.jar:2.9.6] 在 com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719) ~[jackson-databind-2.9.6.jar:2.9.6] 在 com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155) ~[jackson-databind-2.9.6.jar:2.9.6] 在 com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727) ~[jackson-databind-2.9.6.jar:2.9.6] 在 com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719) ~[jackson-databind-2.9.6.jar:2.9.6] 在 com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155) ~[jackson-databind-2.9.6.jar:2.9.6] 在 com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480) ~[jackson-databind-2.9.6.jar:2.9.6] 在 com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319) ~[jackson-databind-2.9.6.jar:2.9.6] 在 com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1396) ~[jackson-databind-2.9.6.jar:2.9.6] 在 com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:913) ~[jackson-databind-2.9.6.jar:2.9.6] 在 org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:286) ~[spring-web-4.3.18.RELEASE.jar:4.3.18.RELEASE] ...省略了58个常用框架

kvService 只从 redis 返回数据。它将 Hash 数据类型 (https://redis.io/topics/data-types) 解析为 proto 中定义的 Hash 对象。其中 Hash->category 是 hash 的主键,hash redis 数据类型中的值被转换为 proto 中定义的 KVPair。我不能显示所有源代码,因为它调用其他系统,源代码很长。

kvService 返回有效的 Hash 对象,但是当我返回此 Hash 对象并且 spring 尝试将其转换为 JSON 时发生异常。

我的 build.gradle 中的重要依赖项:

def versions = [
        logback: '1.2.3',
        owner: '1.0.10',
        jackson: '2.9.6',

        guava: '25.1-jre',
        guice: '4.2.0',
        grpc: '1.9.1',
        protoc: '3.5.1',

        redis: '2.9.0',
]

依赖{

compile group: 'ch.qos.logback', name: 'logback-classic', version: versions.logback
compile group: 'org.aeonbits.owner', name: 'owner', version: versions.owner

compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: versions.jackson
compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: versions.jackson
compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: versions.jackson

compile group: 'com.google.guava', name: 'guava', version: versions.guava
compile group: 'com.google.inject', name: 'guice', version: versions.guice
compile group: 'io.grpc', name: 'grpc-netty', version: versions.grpc
compile group: 'io.grpc', name: 'grpc-protobuf', version: versions.grpc
compile group: 'io.grpc', name: 'grpc-stub', version: versions.grpc
compile 'org.glassfish:javax.annotation:10.0-b28'


compile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.2.1'
compile group: 'javax.activation', name: 'activation', version: '1.1.1'

compile group: 'redis.clients', name: 'jedis', version: versions.redis

}

正如您在我的 protobuf 定义中看到的那样,它不是任何自引用。

有没有办法解决这个问题?

【问题讨论】:

  • 哪一行代码发生异常?你能分享你的kvservice代码吗?我认为有了这些信息,人们会更容易回答你的问题。

标签: java spring-boot exception jackson protocol-buffers


【解决方案1】:

要将 protobuf 对象转换为 JSON,您应该使用 com.google.protobuf.util.JsonFormat 包中的以下类:

JsonFormat.printer().print()

【讨论】:

    【解决方案2】:

    如果您正在使用 Spring WebFlux 并尝试使用 produces application/json 您可以执行以下操作以使其适用于所有返回 protobuf Message 类型的映射:

    @Configuration
    @EnableWebFlux
    public class WebConfig implements WebFluxConfigurer {
    
    @Override
    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        configurer.defaultCodecs().jackson2JsonEncoder(
            new Jackson2JsonEncoder(Jackson2ObjectMapperBuilder.json().serializerByType(
                    Message.class, new JsonSerializer<Message>() {
                        @Override
                        public void serialize(Message value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
                            String str = JsonFormat.printer().omittingInsignificantWhitespace().print(value);
                            gen.writeRawValue(str);
                        }
                    }
            ).build())
        );
    }
    

    【讨论】:

    • 它非常适合序列化,知道如何适应反序列化。
    【解决方案3】:

    UnknownFieldSet(通过生成的方法Hash.getUnknownFields() 到达)包含getter getDefaultInstanceForType(),它返回UnknownFieldSet 的单例实例。这个单例实例在 getDefaultInstanceForType() 中引用了自己,而 Jackson-databind 无法自动处理这个问题(参见下面的编辑 2)。

    您可能希望使用com.google.protobuf:protobuf-java-util 中的JsonFormat,它使用canonical encoding 而不是Jackson。

    祝你好运!

    编辑>对于春天有ProtobufJsonFormatHttpMessageConverter

    EDIT2> 当然,您可以使用Mix-in Annotations 处理这种情况,但恕我直言,JsonFormat 绝对是要走的路...

    【讨论】:

    • 有效吗?我应该在哪里更新 httpmessageconverter?
    • @Prasath 看看例如herehere。祝你好运!
    猜你喜欢
    • 2021-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-06
    • 2017-09-01
    • 2020-12-22
    • 1970-01-01
    • 2019-03-19
    相关资源
    最近更新 更多