在gRPC

时间:2017-05-16 15:52:13

标签: java protocol-buffers

假设我有两种原型缓冲区类型:

message MessageType1 {
  SomeType1 field1 = 1;
  SomeType2 field2 = 2;
  SomeType3 field3 = 3;
}

message MessageType2 {
  SomeType1 field1 = 1;
  SomeType2 field2 = 2;
  SomeType4 field4 = 3;
}

然后在Java中我希望能够将一个对象用作另一个对象的模板:

MessageType1 message1 = ...;
MessageType2 message2 = MessageType2.newBuilder()
    .usingTemplate(message1)  // sets field1 & field2 only
    .setField4(someValue)
    .build()

而不是

MessageType1 message1 = ...;
MessageType2 message2 = MessageType2.newBuilder()
    .setField1(message1.getField1())
    .setField2(message1.getField2())
    .setField4(someValue)
    .build()

为什么我需要这个?我的gRPC服务旨在获取一种类型的传入数据(message1几乎与另一种不同类型的消息(message2)相同 - 这需要是送出。相同字段的数量巨大,复制代码很平常。如果添加新字段,手动解决方案也有一个缺点。

存在一个模板方法(object.newBuilder(template)),它允许相同类型的模板对象,但是如何在不同类型之间进行模板化?

当然,我可以编写一个小的反射工具来检查所有成员(方法?)并手动复制数据,但生成的代码对于这种任务看起来令人沮丧和丑陋。

有没有什么好方法可以解决这个问题?

2 个答案:

答案 0 :(得分:1)

回答您的具体问题:不,没有基于模板的方法来执行此操作。但是,还有其他一些方法可以达到同样的效果:

  • 如果您不关心性能并且消息之间的字段编号相同,则可以将第一个消息序列化为字节并将其反序列化为新消息。这要求第一条消息中的所有字段必须与第二条消息中的字段的类型和ID号相匹配(但是,第二条消息可以包含其他字段)。这可能不是一个好主意。

  • 将公共字段解压缩到另一条消息,然后共享该消息。例如:

proto:

message Common {
  SomeType1 field1 = 1;
  SomeType2 field2 = 2;
  SomeType3 field3 = 3;
} 

message MessageType1 {
  Common common = 1;
  // ...
}

message MessageType2 {
  Common common = 1;
  // ...
}

然后,您可以在代码中共享消息:

MessageType1 message1 = ...;
MessageType2 message2 = MessageType2.newBuilder()
    .setCommon(message1.getCommon())
    .build();

这可能是更好的解决方案。

  • 最后,如你所说,你可以求助于反思。这可能是最冗长,最慢的方式,但它可以让您获得最大的控制权(除了手动复制字段外)。不推荐。

答案 1 :(得分:1)

事实证明并非如此复杂。我写了一个小实用程序来评估和匹配FieldDescriptors(gRPC生成的东西)。在我的世界中,按名称和类型匹配它们就足够了。完整的解决方案:

/**
 * Copies fields from source to dest. Only copies fields if they are set, have matching name and type as their counterparts in dest.
 */
public static void copyCommonFields(@Nonnull GeneratedMessageV3 source, @Nonnull com.google.protobuf.GeneratedMessageV3.Builder<?> destBuilder) {
    Map<FieldDescriptorKeyElements, Descriptors.FieldDescriptor> elementsInSource = Maps.uniqueIndex(source.getDescriptorForType().getFields(), FieldDescriptorKeyElements::new);
    Map<FieldDescriptorKeyElements, Descriptors.FieldDescriptor> elementsInDest = Maps.uniqueIndex(destBuilder.getDescriptorForType().getFields(), FieldDescriptorKeyElements::new);
    // those two above could even be cached if necessary as this is static info

    Set<FieldDescriptorKeyElements> elementsInBoth = Sets.intersection(elementsInSource.keySet(), elementsInDest.keySet());

    for (Map.Entry<Descriptors.FieldDescriptor, Object> entry : source.getAllFields().entrySet()) {
        Descriptors.FieldDescriptor descriptor = entry.getKey();
        FieldDescriptorKeyElements keyElements = new FieldDescriptorKeyElements(descriptor);
        if (entry.getValue() != null && elementsInBoth.contains(keyElements)) {
            destBuilder.setField(elementsInDest.get(keyElements), entry.getValue());
        }
    }
}

// used for convenient/quick lookups in a Set
private static final class FieldDescriptorKeyElements {
    final String fieldName;
    final Descriptors.FieldDescriptor.JavaType javaType;
    final boolean isRepeated;

    private FieldDescriptorKeyElements(Descriptors.FieldDescriptor fieldDescriptor) {
        this.fieldName = fieldDescriptor.getName();
        this.javaType = fieldDescriptor.getJavaType();
        this.isRepeated = fieldDescriptor.isRepeated();
    }

    @Override
    public int hashCode() {
        return Objects.hash(fieldName, javaType, isRepeated);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null || !(obj instanceof FieldDescriptorKeyElements)) {
            return false;
        }
        FieldDescriptorKeyElements other = (FieldDescriptorKeyElements) obj;
        return Objects.equals(this.fieldName, other.fieldName) &&
                Objects.equals(this.javaType, other.javaType) &&
                Objects.equals(this.isRepeated, other.isRepeated);
    }
}
相关问题