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

如何为GRPC Server Interceptor绑定注解并获取调用方法详情

在gRPC Server拦截器中基于注解实现方法级条件拦截

要实现拦截器仅对带有指定注解的gRPC方法执行处理,核心是关联gRPC方法与注解元数据,再在拦截器中校验匹配。下面提供两种可行方案,分别基于Java注解和Protobuf自定义选项:

方案一:基于Java注解实现

1. 定义自定义方法注解

先创建一个运行时可见的注解,标记需要拦截的方法:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface GrpcIntercept {
    // 可添加自定义属性,比如拦截器类型、权限标识等
}

2. 在业务服务接口上标记注解

在你自己定义的gRPC业务接口(而非自动生成的Stub接口)的目标方法上添加注解:

public interface MyBusinessService {
    @GrpcIntercept
    void sayHello(HelloRequest request, StreamObserver<HelloResponse> responseObserver);

    // 无需拦截的方法不标记注解
    void sayHi(HiRequest request, StreamObserver<HiResponse> responseObserver);
}

确保自动生成的gRPC服务实现类(如MyServiceGrpc.MyServiceImplBase的子类)实现了这个业务接口。

3. 预构建方法-注解映射缓存

在应用启动时扫描所有gRPC服务实例,收集带有@GrpcIntercept注解的方法,建立gRPC方法全限定名与注解的映射(避免拦截器中重复反射影响性能):

import io.grpc.BindableService;
import io.grpc.MethodDescriptor;
import io.grpc.ServiceDescriptor;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
public class GrpcAnnotationCache {
    private final Map<String, GrpcIntercept> methodAnnotationMap = new HashMap<>();

    public GrpcAnnotationCache(List<BindableService> grpcServices) {
        for (BindableService service : grpcServices) {
            Class<?> serviceClass = service.getClass();
            // 遍历服务实现的业务接口(排除gRPC自动生成的Stub接口)
            for (Class<?> iface : serviceClass.getInterfaces()) {
                if (iface.getName().endsWith("BusinessService")) {
                    for (Method method : iface.getDeclaredMethods()) {
                        GrpcIntercept annotation = method.getAnnotation(GrpcIntercept.class);
                        if (annotation != null) {
                            try {
                                // 获取gRPC服务描述符,找到对应方法的全限定名
                                ServiceDescriptor serviceDescriptor = (ServiceDescriptor) serviceClass.getMethod("getServiceDescriptor").invoke(service);
                                // 注意:proto方法名首字母大写,Java方法名是驼峰,需转换匹配
                                String protoMethodName = capitalizeFirstLetter(method.getName());
                                MethodDescriptor<?, ?> methodDescriptor = serviceDescriptor.findMethodByName(protoMethodName);
                                if (methodDescriptor != null) {
                                    methodAnnotationMap.put(methodDescriptor.getFullMethodName(), annotation);
                                }
                            } catch (Exception e) {
                                throw new RuntimeException("初始化gRPC注解缓存失败", e);
                            }
                        }
                    }
                }
            }
        }
    }

    private String capitalizeFirstLetter(String str) {
        if (str == null || str.isEmpty()) return str;
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }

    public GrpcIntercept getAnnotation(String fullMethodName) {
        return methodAnnotationMap.get(fullMethodName);
    }
}

4. 在拦截器中校验注解并执行逻辑

注入缓存实例,在拦截器中获取当前调用的方法全限定名,匹配注解后执行拦截逻辑:

import io.grpc.*;

public class AnnotationInterceptor implements ServerInterceptor {
    private final GrpcAnnotationCache annotationCache;

    public AnnotationInterceptor(GrpcAnnotationCache annotationCache) {
        this.annotationCache = annotationCache;
    }

    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
        String fullMethodName = call.getMethodDescriptor().getFullMethodName();
        GrpcIntercept annotation = annotationCache.getAnnotation(fullMethodName);

        if (annotation != null) {
            // 执行自定义拦截逻辑,比如日志、权限校验、参数校验等
            System.out.println("执行拦截处理:" + fullMethodName);
            // 包装Listener处理请求/响应生命周期
            return new ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(next.startCall(call, headers)) {
                @Override
                public void onMessage(ReqT message) {
                    // 可在此处理请求消息,比如脱敏、校验
                    super.onMessage(message);
                }

                @Override
                public void onHalfClose() {
                    // 请求发送完成后的处理
                    super.onHalfClose();
                }
            };
        }

        // 无注解,直接放行
        return next.startCall(call, headers);
    }
}

方案二:基于Protobuf自定义选项实现(更原生)

如果不想依赖Java反射,可直接在Protobuf定义中添加自定义选项,标记需要拦截的方法,这种方式更贴合gRPC原生机制,也支持跨语言场景。

1. 在proto文件中定义自定义选项

syntax = "proto3";

import "google/protobuf/descriptor.proto";

// 定义方法级自定义选项,编号需在50001-99999的自定义范围内
extend google.protobuf.MethodOptions {
    bool need_intercept = 50001;
}

service MyService {
    rpc SayHello(HelloRequest) returns (HelloResponse) {
        // 标记该方法需要拦截
        option (need_intercept) = true;
    }
    rpc SayHi(HiRequest) returns (HiResponse);
}

message HelloRequest {
    string name = 1;
}

message HelloResponse {
    string message = 1;
}

message HiRequest {
    string name = 1;
}

message HiResponse {
    string message = 1;
}

2. 在拦截器中读取自定义选项

直接从MethodDescriptor中获取自定义选项的值,无需反射:

import io.grpc.*;
import your.package.MyProto; // 替换为你的proto生成类路径

public class OptionBasedInterceptor implements ServerInterceptor {
    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
        MethodDescriptor<ReqT, RespT> methodDescriptor = call.getMethodDescriptor();
        // 获取自定义选项的值
        Boolean needIntercept = methodDescriptor.getOptions().getExtension(MyProto.need_intercept);

        if (needIntercept != null && needIntercept) {
            // 执行拦截逻辑
            return new ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(next.startCall(call, headers)) {
                @Override
                public void onMessage(ReqT message) {
                    // 自定义处理逻辑
                    super.onMessage(message);
                }
            };
        }

        return next.startCall(call, headers);
    }
}

方案对比

方案优点缺点
Java注解方式符合Java开发者习惯,无需修改proto依赖反射,需处理Java与proto方法名映射
Protobuf自定义选项原生支持,无反射,跨语言友好需要修改proto文件,注解属性扩展受限

内容的提问来源于stack exchange,提问作者Narain

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 20:14:51