Java:gRPC中如何使用json格式的报文发起RPC调用

      gRPC默认是使用自定义的二进制格式传输报文的,但gRPC不只是能通过二进制格式传输报文,它也可以使用json等其它格式传输报文。

gRPC使用json格式传输报文的例子:
https://github.com/grpc/grpc-java/tree/master/examples/src/main/java/io/grpc/examples/advanced

      从这个例子中可以看到,通过扩展io.grpc.MethodDescriptor.Marshaller,实现了对象与json格式文本的互相转换。以下方法创建了一个对象与json格式文本可互相转换的io.grpc.MethodDescriptor.Marshaller实例:

public static <T extends Message> Marshaller<T> jsonMarshaller(
  final T defaultInstance, final Parser parser, final Printer printer) {

final Charset charset = Charset.forName("UTF-8");

return new Marshaller<T>() {
  @Override
  public InputStream stream(T value) {
    try {
      return new ByteArrayInputStream(printer.print(value).getBytes(charset));
    } catch (InvalidProtocolBufferException e) {
      throw Status.INTERNAL
          .withCause(e)
          .withDescription("Unable to print json proto")
          .asRuntimeException();
    }
  }

  @SuppressWarnings("unchecked")
  @Override
  public T parse(InputStream stream) {
    Builder builder = defaultInstance.newBuilderForType();
    Reader reader = new InputStreamReader(stream, charset);
    T proto;
    try {
      parser.merge(reader, builder);
      proto = (T) builder.build();
      reader.close();
    } catch (InvalidProtocolBufferException e) {
      throw Status.INTERNAL.withDescription("Invalid protobuf byte sequence")
          .withCause(e).asRuntimeException();
    } catch (IOException e) {
      // Same for now, might be unavailable
      throw Status.INTERNAL.withDescription("Invalid protobuf byte sequence")
          .withCause(e).asRuntimeException();
    }
    return proto;
  }
};
}

然后,使用这个Marshaller实例创建了MethodDescriptor<HelloRequest, HelloReply>对象:

static final MethodDescriptor<HelloRequest, HelloReply> METHOD_SAY_HELLO = GreeterGrpc.getSayHelloMethod()
        .toBuilder(JsonMarshaller.jsonMarshaller(HelloRequest.getDefaultInstance()),
                JsonMarshaller.jsonMarshaller(HelloReply.getDefaultInstance()))
        .build();

      在服务器端,GreeterImpl没有直接继承GreeterGrpc.GreeterImplBase,而是直接继承io.grpc.BindableService。因为GreeterGrpc.GreeterImplBase中的接口方法public io.grpc.ServerServiceDefinition bindService();已有默认实现(默认实现使用的是二进制报文),并且此实现方法被标记为final,因此通过继承GreeterGrpc.GreeterImplBase,并覆盖public io.grpc.ServerServiceDefinition bindService();已变得不可能。所以例子中服务器端代码直接继承io.grpc.BindableService接口,实现了其中的public io.grpc.ServerServiceDefinition bindService();方法,在此方法中注册了的MethodDescriptor<HelloRequest, HelloReply>,它包含有io.grpc.MethodDescriptor.Marshaller的json实现。

       同时,在客户端,例子中没有直接使用GreeterGrpc.GreeterBlockingStub,而是直接实现了io.grpc.stub.AbstractStub,并在其中使用了上面的MethodDescriptor<HelloRequest, HelloReply>。

       这个例子虽然实现了使用json格式作为报文格式,但需要对每个服务的每个RPC方法提供相对应的实现,如果项目中服务很多的话,每个都要在客户端代码和服务器端代码中实现一遍,这样一定非常麻烦,还容易出错。那有没有全局性的设置方法,只要在一个地方设置一次,所有的RPC调用都可以使用json格式传输报文呢?

       本人在gRPC库中没有找到直接实现的方式(如果有谁找到这样的方法,请告之于我),但本人通过一些其它方式,实现了近似的功能。下面就是实现的代码:

首先,需要一个在json与object之间进行转换的Marshaller:

package com.example.tutorial.tool;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

import com.google.common.io.CharStreams;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.Message.Builder;
import com.google.protobuf.util.JsonFormat;
import com.google.protobuf.util.JsonFormat.Parser;
import com.google.protobuf.util.JsonFormat.Printer;

import io.grpc.MethodDescriptor.Marshaller;
import io.grpc.Status;

/**
 * 使用json格式传输报文,需要使用此报文轮换器
 *
 * @param <T>
 */
public final class JsonMarshaller<T extends Message> implements Marshaller<T> {

  private final Parser parser = JsonFormat.parser();
  private final Printer printer = JsonFormat.printer().omittingInsignificantWhitespace();

  private final T defaultInstance;

  public JsonMarshaller(T defaultInstance) {
    this.defaultInstance = defaultInstance;
  }

  @Override
  public InputStream stream(T value) {
    try {
      return new ByteArrayInputStream(printer.print(value).getBytes(StandardCharsets.UTF_8));
    } catch (InvalidProtocolBufferException e) {
      throw Status.INTERNAL.withCause(e).withDescription("Unable to create json").asRuntimeException();
    }
  }

  @SuppressWarnings("unchecked")
  @Override
  public T parse(InputStream stream) {
    T proto = null;
    try {
      Builder builder = defaultInstance.newBuilderForType();
      String text = CharStreams.toString(new InputStreamReader(stream, StandardCharsets.UTF_8));

      parser.merge(text, builder);
      proto = (T) builder.build();
    } catch (Exception e) {
      throw Status.INTERNAL.withDescription("Unable to parse json").withCause(e).asRuntimeException();
    }

    return proto;
  }
}

 然后,需要对所有gRPC服务注册上面的Marshaller:

package com.example.tutorial.tool;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.protobuf.Message;

import io.grpc.MethodDescriptor;
import io.grpc.MethodDescriptor.Marshaller;
import io.grpc.MethodDescriptor.PrototypeMarshaller;

public class GlobalMarshallerInitializer {

  private static final Logger LOG = Logger.getLogger(GlobalMarshallerInitializer.class.getName());

  // 注册定制的Marshaller
  public static void initiateMarshaller(Class<?> serviceClass,
      Class<? extends Marshaller<? extends Message>> marshallerClass) {
    try {
      Field serviceNameField = serviceClass.getField("SERVICE_NAME");
      if (serviceNameField == null) {
        return;
      }

      for (Method m : serviceClass.getDeclaredMethods()) {

        if (Modifier.isStatic(m.getModifiers()) && m.getReturnType() == MethodDescriptor.class) {

          // 为各RPC调用方法生成默认的MethodDescriptor,默认MethodDescriptor中的Marshaller使用的是二进制格式传输报文
          MethodDescriptor<?, ?> md = (MethodDescriptor<?, ?>) m.invoke(null);

          Field requestMarshallerField = md.getClass().getDeclaredField("requestMarshaller");
          Field responseMarshallerField = md.getClass().getDeclaredField("responseMarshaller");

          Object requestMessagePrototype = ((PrototypeMarshaller<?>) md.getRequestMarshaller())
              .getMessagePrototype();
          Object responseMessagePrototype = ((PrototypeMarshaller<?>) md.getResponseMarshaller())
              .getMessagePrototype();

          Marshaller<? extends Message> requestMarshallerInstance = marshallerClass
              .getConstructor(Message.class).newInstance(requestMessagePrototype);
          Marshaller<? extends Message> responseMarshallerInstance = marshallerClass
              .getConstructor(Message.class).newInstance(responseMessagePrototype);

          // 使用反射机制设置输入Marshaller
          requestMarshallerField.setAccessible(true);
          requestMarshallerField.set(md, requestMarshallerInstance);
          requestMarshallerField.setAccessible(false);

          // 使用反射机制设置输出Marshaller
          responseMarshallerField.setAccessible(true);
          responseMarshallerField.set(md, responseMarshallerInstance);
          responseMarshallerField.setAccessible(false);

          LOG.info("finish to initiate method: " + md.getFullMethodName());
        }
      }
    } catch (Exception e) {
      LOG.log(Level.SEVERE, "fail to initiate service " + serviceClass.getName(), e);
    }
  }
}

最后,只需要在服务端代码和客户端代码中,对需要使用json格式传输报文的服务,调用上述代码进行初始化:

GlobalMarshallerInitializer.initiateMarshaller(GreeterGrpc.class,
				(Class<? extends Marshaller<? extends Message>>) JsonMarshaller.class);

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值