grpc使用小结
背景:
项目采用DDD架构,接口都是grpc风格,
所以拿到request后,需要将grpc对象转换为DDD中的domain对象;
在response中,将domain对象转换为grpc对象。
mapstuct版本
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.5.Final</version>
</dependency>
1.grpc对象中的基本数据类型无法传递null值
解决办法:引入google的包装类
import "google/protobuf/wrappers.proto";
eg:
点击查看proto文件
syntax = "proto3";
package userService;
option java_package = "com.grpc.auto.UserService";
option java_multiple_files = true;
import "google/protobuf/wrappers.proto";
service UserService {
rpc addUser(AddUserRequest) returns (AddUserResponse);
}
message AddUserRequest {
string userName = 1;
int32 age = 2;
int64 registryTime = 3;
google.protobuf.StringValue userNameWrapper = 4;
google.protobuf.Int32Value ageWrapper = 5;
google.protobuf.Int64Value registryTimeWrapper = 6;
}
message AddUserResponse {
google.protobuf.StringValue code = 1;
google.protobuf.StringValue message = 2;
google.protobuf.StringValue data = 3;
}
点击查看grpc接口
import com.google.protobuf.StringValue;
import com.grpc.auto.UserService.AddUserRequest;
import com.grpc.auto.UserService.AddUserResponse;
import com.grpc.auto.UserService.AddUserResponse.Builder;
import com.grpc.auto.UserService.UserServiceGrpc.UserServiceImplBase;
import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.server.service.GrpcService;
@GrpcService
@Slf4j
public class UserInputAdapter extends UserServiceImplBase {
@Override
public void addUser(AddUserRequest request, StreamObserver<AddUserResponse> responseObserver) {
Builder builder = AddUserResponse.newBuilder()
.setCode(StringValue.of("200"))
.setMessage(StringValue.of("success"))
.setData(StringValue.of("user Data."));
responseObserver.onNext(builder.build());
responseObserver.onCompleted();
}
}
正常调用


基础类型不管是传null还是不传递都有值(String是"",其他类型是默认值)
包装类型传null或者不传递就可以为null


2.统一的响应体
想要提供给调用方一个统一的响应体,那就要用到泛型 ~ grpc里的Any.
目前我还没想到更好的方式, 所以结果是list和 结果是单个对象的我暂时用两个字段来接收了。
eg:
点击查看proto文件
syntax = "proto3";
package userService;
option java_package = "com.grpc.auto.UserService";
option java_multiple_files = true;
import "google/protobuf/wrappers.proto";
import "google/protobuf/any.proto";
service UserService {
rpc addUser(AddUserRequest) returns (AddUserResponse);
}
message AddUserRequest {
string userName = 1;
int32 age = 2;
int64 registryTime = 3;
google.protobuf.StringValue userNameWrapper = 4;
google.protobuf.Int32Value ageWrapper = 5;
google.protobuf.Int64Value registryTimeWrapper = 6;
}
message AddUserResponse {
google.protobuf.StringValue code = 1;
google.protobuf.StringValue message = 2;
google.protobuf.Any data = 3;
repeated google.protobuf.Any datas = 4;
}
点击查看grpc接口
@Override
public void addUser(AddUserRequest request, StreamObserver<AddUserResponse> responseObserver) {
Builder builder = AddUserResponse.newBuilder()
.setCode(StringValue.of("200"))
.setMessage(StringValue.of("success"))
.setData(Any.pack(StringValue.of("user Data.")))
.addAllDatas(List.of(
Any.pack(StringValue.of("user Data111111111.")),
Any.pack(StringValue.of("user Data222222222."))
));
responseObserver.onNext(builder.build());
responseObserver.onCompleted();
}
点击查看response
{
"datas": [
{
"value": "user Data111111111.",
"@type": "type.googleapis.com/google.protobuf.StringValue"
},
{
"value": "user Data222222222.",
"@type": "type.googleapis.com/google.protobuf.StringValue"
}
],
"code": {
"value": "200"
},
"message": {
"value": "success"
},
"data": {
"value": "user Data.",
"@type": "type.googleapis.com/google.protobuf.StringValue"
}
}
3.mapstruct与grpc配合使用
- mapstruct公共配置
@MapperConfig(
componentModel = "spring",
unmappedTargetPolicy = ReportingPolicy.IGNORE,
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,
nullValueIterableMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT,
uses = MapstructUtil.class
)
public class MapstructConfig {
}
- mapstruct的一个工具类,提供了一些简单的转换方法
点击查看代码
package xxx;
import com.google.protobuf.DoubleValue;
import com.google.protobuf.Int32Value;
import com.google.protobuf.Int64Value;
import com.google.protobuf.StringValue;
import com.google.protobuf.StringValue.Builder;
import org.mapstruct.Named;
import org.springframework.stereotype.Component;
import java.sql.Timestamp;
/**
* Author:yangkang
* Date:2025/3/26 15:34
* Description: 进入这个类的参数应该使用其他注解确保进入时已经进行了null检查,此处无需进行null校验。
*/
@Component
public class MapstructUtil {
// ================= grpcValue类型装箱 ==========
@Named("pack")
public static StringValue pack(String string) {
Builder builder = StringValue.newBuilder();
builder.setValue(string);
return builder.build();
}
@Named("pack")
public static Int32Value pack(Integer integer) {
Int32Value.Builder builder = Int32Value.newBuilder();
builder.setValue(integer);
return builder.build();
}
@Named("pack")
public static Int64Value pack(Long longParam) {
Int64Value.Builder builder = Int64Value.newBuilder();
builder.setValue(longParam);
return builder.build();
}
@Named("packTimestamp")
public static Int64Value packTimestamp(Timestamp timestamp) {
Int64Value.Builder builder = Int64Value.newBuilder();
builder.setValue(timestamp.getTime());
return builder.build();
}
@Named("pack")
public static DoubleValue pack(Double doubleParam) {
DoubleValue.Builder builder = DoubleValue.newBuilder();
builder.setValue(doubleParam);
return builder.build();
}
// ================= grpcValue类型拆箱 ==========
@Named("unpack")
public static String unpack(StringValue stringValue) {
return stringValue.getValue();
}
@Named("unpack")
public static Integer unpack(Int32Value int32Value) {
return int32Value.getValue();
}
@Named("unpack")
public static Long unpack(Int64Value int64Value) {
return int64Value.getValue();
}
@Named("unpack")
public static Double unpack(DoubleValue doubleValue) {
return doubleValue.getValue();
}
@Named("unpackTimestamp")
public static Timestamp unpackTimeStamp(Int64Value time) {
return new Timestamp(time.getValue());
}
// ================= 其他 ==========
@Named("longToTimestamp")
public Timestamp longToTimestamp(long time) {
return time != 0L ? new Timestamp(time) : null;
}
@Named("longValueToTimestamp")
public Timestamp longValueToTimestamp(Long time) {
return time == null ? null : new Timestamp(time);
}
@Named("timestampToLong")
public long timestampToLong(Timestamp timestamp) {
return timestamp != null ? timestamp.getTime() : 0L;
}
@Named("timestampToLongValue")
public Long timestampToLongValue(Timestamp timestamp) {
return timestamp != null ? timestamp.getTime() : null;
}
}
- 在mapper中使用配置类
点击查看代码
@Mapper(config = MapstructConfig.class, collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED)
public interface RealTimeModelChartDtoConvert {
// 两个对象中都有一个double类型的字段 doubleField,不过都是包装类型
@Mapping(target = "doubleField", qualifiedByName = "pack")
TestGrpc toTestGrpc(TestDto testDto);
}
4.grpc对象的打印
点击查看代码
@Slf4j
public class GrpcUtil {
/**
* 转成有格式的json字符串
*/
public static String toJsonStr(Object obj) {
if (obj == null) {
return null;
}
try {
if (obj instanceof MessageOrBuilder message) {
Printer printer = JsonFormat.printer().includingDefaultValueFields();
TypeRegistry registry = TypeRegistry.newBuilder().add(message.getDescriptorForType()).build();
printer = printer.usingTypeRegistry(registry);
return printer.print(message);
} else {
log.warn("message is not a protobuf Message");
}
} catch (Exception e) {
log.error("error when execute toJsonStr.", e);
}
return null;
}
/**
* 转成无格式的单行json字符串
*/
public static String toJsonStrSimple(Object obj) {
if (obj == null) {
return null;
}
try {
if (obj instanceof MessageOrBuilder message) {
Printer printer = JsonFormat.printer().includingDefaultValueFields().omittingInsignificantWhitespace();
TypeRegistry registry = TypeRegistry.newBuilder().add(message.getDescriptorForType()).build();
printer = printer.usingTypeRegistry(registry);
return printer.print(message);
} else {
log.warn("message is not a protobuf Message");
}
} catch (Exception e) {
log.error("error when execute toJsonStr.", e);
}
return null;
}
}

浙公网安备 33010602011771号