You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 20:35:29