【问题标题】:Geting list of protobuf message objects from the SpringBoot从 Spring Boot 获取 protobuf 消息对象列表
【发布时间】:2020-05-10 23:26:49
【问题描述】:

我想从 Spring Boot 应用程序中获取 protobuf 消息对象的列表。
我确实设法从应用程序中获取了一个 protobuf 消息对象,但获取它们的列表会引发异常。

...
2020-01-24 14:57:02.359 ERROR 15883 --- [nio-8081-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class com.google.protobuf.UnknownFieldSet$Parser]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com.google.protobuf.UnknownFieldSet$Parser and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: java.util.ImmutableCollections$ListN[0]->com.example.demo.Lecture["unknownFields"]->com.google.protobuf.UnknownFieldSet["parserForType"])] with root cause

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com.google.protobuf.UnknownFieldSet$Parser and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: java.util.ImmutableCollections$ListN[0]->com.example.demo.Lecture["unknownFields"]->com.google.protobuf.UnknownFieldSet["parserForType"])
    at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77) ~[jackson-databind-2.10.2.jar:2.10.2]
...

我的代码(简化)。

tl;博士

  • 创建 Spring Boot 应用
  • proto 文件生成类
  • 尝试返回List 生成的类对象(RESTful)

我的代码(简化)。
控制器

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@Slf4j
@org.springframework.web.bind.annotation.RestController
@RequestMapping("/timetable")
public class RestController {

    @PostMapping("/single")      // Works
    private Lecture getLecture(@RequestBody Lecture lecture) {
        log.info("Single2 got: {}", lecture);
        return Lecture.newBuilder(lecture)
                .setDuration(lecture.getDuration() +1)
                .build();
    }

    @GetMapping("/list")       // Does not work
    private @ResponseBody List<Lecture> getLectures() {
        return List.of(
                Lecture.newBuilder()
                        .setDuration(1)
                        .setWeekDay(Lecture.WeekDay.MONDAY)
                        .setModule(Module.newBuilder().setName("Math1").build())
                        .build()
                // ... 
        );
    }
}

应用程序

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter;
import org.springframework.http.converter.protobuf.ProtobufJsonFormatHttpMessageConverter;

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    @Primary
    ProtobufHttpMessageConverter protobufHttpMessageConverter() {
        return new ProtobufJsonFormatHttpMessageConverter();
    }
}

pom.xml

<!-- ... -->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--        https://dzone.com/articles/exposing-microservices-over-rest-protocol-buffers-->
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>3.11.1</version>
        </dependency>
        <dependency>
            <groupId>com.googlecode.protobuf-java-format</groupId>
            <artifactId>protobuf-java-format</artifactId>
            <version>1.4</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java-util -->
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java-util</artifactId>
            <version>3.11.1</version>
        </dependency>
    </dependencies>
<!-- ... -->

我使用以下方法生成消息对象:

#!/bin/bash
SRC_DIR=../proto
DST_DIR=../../../target/
mkdir -p $DST_DIR
protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/college.proto

原型文件

syntax = "proto3";

package my.college;

option java_multiple_files = true;
option java_package = "com.example.demo";

message Module {
    string name = 1;
    // ... other
}

message Lecture {
    WeekDay weekDay = 1;
    Module module = 2;
    uint32 duration = 3;
    // ... other

    enum WeekDay {
        SUNDAY = 0;
        MONDAY = 1;
        // ...
    }
}

我确实找到了simmilar issue,但没有解决方案。

【问题讨论】:

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


    【解决方案1】:

    说明

    Spring 将为您的响应正文的对象类型选择与适当转换器匹配的HttpMessageConverter。在这种情况下,很可能会选择 MappingJackson2HttpMessageConverter 中的 MappingJackson2HttpMessageConverter,因为您的响应正文具有 List 类型。

    MappingJackson2HttpMessageConverter 实现 HttpMessageConverter&lt;Object&gt; ProtobufJsonFormatHttpMessageConverter 实现 HttpMessageConverter&lt;Message&gt;

    由于ProtobufJsonFormatHttpMessageConverter不支持序列化List类型,我们可以通过配置告诉MappingJackson2HttpMessageConverter如何序列化Message类型。

    解决方案

    Jackson2ObjectMapperBuilderCustomizer 类型的 bean 可用于为消息类型注册序列化程序。

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return o -> o.serializerByType(Message.class, new JsonSerializer<Message>() {
            @Override
            public void serialize(Message value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
                gen.writeRawValue(JsonFormat.printer().print(value));
            }
        });
    }
    

    【讨论】:

      【解决方案2】:

      解决方法
      我找不到问题的解决方案,所以想出了一个解决方法。
      我没有返回生成的 protobuf 消息对象,而是返回了这些对象的包装器。使用Lombok注解可以做到:

      import lombok.Data;
      
      @Data // Lombok magic
      public class Module {
          private String name;
          // ...
      
          public Module(ie.gmit.proto.Module moduleProto){
              this.name = moduleProto.getName();
              // ...
          }
      }
      

      这种解决方法感觉不是很糟糕,因为它使用标准的 Spring 引导依赖项。

      【讨论】:

        猜你喜欢
        • 2018-12-19
        • 1970-01-01
        • 1970-01-01
        • 2018-06-19
        • 2017-09-12
        • 2019-10-07
        • 2021-08-01
        • 2019-10-10
        • 2020-07-27
        相关资源
        最近更新 更多