如何在gRPC Spring Boot中自定义Trailing元数据
在Spring Boot gRPC服务端添加自定义Trailing元数据(执行时间)
1. 完善ServerInterceptor实现
你已经实现了ServerInterceptor,接下来需要在interceptCall方法中包装原生ServerCall.Listener,监听调用的完成/取消事件,计算执行时间并写入Trailing元数据。
示例代码:
import io.grpc.*; import org.springframework.stereotype.Component; @Component public class ExecutionTimeInterceptor implements ServerInterceptor { @Override public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) { // 记录请求开始时间 final long startTime = System.currentTimeMillis(); // 包装Listener,监听调用生命周期 return new ForwardingServerCallListener.SimpleForwardingServerCallListener<>(next.startCall(call, headers)) { @Override public void onComplete() { // 计算执行时长 long executionTime = System.currentTimeMillis() - startTime; // 构造Trailing元数据 Metadata trailers = new Metadata(); Metadata.Key<String> EXECUTION_TIME_KEY = Metadata.Key.of("execution-time-ms", Metadata.ASCII_STRING_MARSHALLER); trailers.put(EXECUTION_TIME_KEY, String.valueOf(executionTime)); // 发送元数据 call.sendHeaders(trailers); // 执行原有完成逻辑 super.onComplete(); } @Override public void onCancel() { // 请求取消时也可记录执行时间(可选) long executionTime = System.currentTimeMillis() - startTime; Metadata trailers = new Metadata(); Metadata.Key<String> EXECUTION_TIME_KEY = Metadata.Key.of("execution-time-ms", Metadata.ASCII_STRING_MARSHALLER); trailers.put(EXECUTION_TIME_KEY, String.valueOf(executionTime)); call.sendHeaders(trailers); super.onCancel(); } }; } }
2. 注册Interceptor到gRPC服务端
在Spring Boot环境下,有两种注册方式可选:
方式一:单个服务生效
在你的gRPC服务实现类上,通过@GrpcService注解指定该Interceptor:
import net.devh.boot.grpc.server.service.GrpcService; @GrpcService(interceptors = ExecutionTimeInterceptor.class) public class YourGrpcServiceImpl extends YourGrpcServiceGrpc.YourGrpcServiceImplBase { // 实现你的业务方法 }
方式二:全局所有服务生效
创建配置类,通过GrpcServerConfigurer将Interceptor注册到全局:
import net.devh.boot.grpc.server.config.GrpcServerConfigurer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class GrpcServerConfig { @Bean public GrpcServerConfigurer grpcServerConfigurer(ExecutionTimeInterceptor executionTimeInterceptor) { return serverBuilder -> serverBuilder.intercept(executionTimeInterceptor); } }
3. 客户端接收Trailing元数据(可选)
如果需要客户端获取该执行时间,可在客户端ClientCall.Listener的onClose方法中读取Trailing元数据:
Metadata trailingMetadata = new Metadata(); clientCall.start(new ClientCall.Listener<RespT>() { @Override public void onClose(Status status, Metadata trailers) { super.onClose(status, trailers); Metadata.Key<String> EXECUTION_TIME_KEY = Metadata.Key.of("execution-time-ms", Metadata.ASCII_STRING_MARSHALLER); String executionTime = trailers.get(EXECUTION_TIME_KEY); if (executionTime != null) { System.out.println("服务端执行耗时:" + executionTime + "ms"); } } }, headers);
注意事项
- 元数据Key需符合HTTP规范,建议使用小写字母+连字符的格式,避免特殊字符。
call.sendHeaders()可多次调用,但Trailing元数据建议在onComplete/onCancel阶段发送,确保是调用结束前的最后一批元数据。- 若服务为流式响应,
onComplete会在所有响应发送完毕后触发,此时计算的是整个流式调用的总耗时。
内容的提问来源于stack exchange,提问作者Vu Lenh Tho
相关产品推荐
相关产品推荐

