{"id":18879303,"url":"https://github.com/jdk-plus/spring-boot-starter-grpc","last_synced_at":"2026-05-10T02:41:24.308Z","repository":{"id":53259218,"uuid":"521321079","full_name":"JDK-Plus/spring-boot-starter-grpc","owner":"JDK-Plus","description":"A Springboot extension that integrates GRpc dependencies🏕⛵️🌋🛳","archived":false,"fork":false,"pushed_at":"2024-06-21T06:53:18.000Z","size":81,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-12-31T02:43:52.731Z","etag":null,"topics":["grpc","springboot"],"latest_commit_sha":null,"homepage":"https://jdk.plus/zh/spring-boot-starter-grpc/","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JDK-Plus.png","metadata":{"files":{"readme":"README-CN.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-08-04T15:34:08.000Z","updated_at":"2024-12-03T02:58:35.000Z","dependencies_parsed_at":"2024-06-21T23:58:59.012Z","dependency_job_id":"c5f8a78c-ff6a-410f-af24-fabfacafe749","html_url":"https://github.com/JDK-Plus/spring-boot-starter-grpc","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JDK-Plus%2Fspring-boot-starter-grpc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JDK-Plus%2Fspring-boot-starter-grpc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JDK-Plus%2Fspring-boot-starter-grpc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JDK-Plus%2Fspring-boot-starter-grpc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JDK-Plus","download_url":"https://codeload.github.com/JDK-Plus/spring-boot-starter-grpc/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239841743,"owners_count":19705981,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["grpc","springboot"],"created_at":"2024-11-08T06:35:10.668Z","updated_at":"2026-02-20T00:30:17.745Z","avatar_url":"https://github.com/JDK-Plus.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n\u003ch3 align=\"center\"\u003e一个集成GRpc依赖的Springboot扩展\u003c/h3\u003e\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://github.com/JDK-Plus/spring-boot-starter-grpc/blob/master/LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/github/license/JDK-Plus/spring-boot-starter-grpc.svg\" /\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/JDK-Plus/spring-boot-starter-grpc/releases\"\u003e\u003cimg src=\"https://img.shields.io/github/release/JDK-Plus/spring-boot-starter-grpc.svg\" /\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/JDK-Plus/spring-boot-starter-grpc/stargazers\"\u003e\u003cimg src=\"https://img.shields.io/github/stars/JDK-Plus/spring-boot-starter-grpc.svg\" /\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/JDK-Plus/spring-boot-starter-grpc/network/members\"\u003e\u003cimg src=\"https://img.shields.io/github/forks/JDK-Plus/spring-boot-starter-grpc.svg\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\n## 引入依赖\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eplus.jdk.grpc\u003c/groupId\u003e\n    \u003cartifactId\u003espring-boot-starter-grpc\u003c/artifactId\u003e\n    \u003cversion\u003e1.1.07\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## 需要添加的配置项\n\n```\n# 是否开启grpc server\nplus.jdk.grpc.enabled=true\n\nplus.jdk.grpc.client.enabled=true\n\n# 指定端口\nplus.jdk.grpc.port=10400\n\n# 指定监听的服务地址\nplus.jdk.grpc.address=*\n\n# 是否支持长连接\nplus.jdk.grpc.enable-keep-alive=true\n\n# 长连接超时断开时间\nplus.jdk.grpc.keep-alive-timeout=111\n\n# NioEventLoopGroup master核心线程数\nplus.jdk.grpc.master-thread-num=1\n\n# NioEventLoopGroup worker线程数\nplus.jdk.grpc.worker-thread-num=10\n\n# 数据包最大多少字节\nplus.jdk.grpc.max-inbound-message-size=100000\n\n# 发送的请求头最大限制\nplus.jdk.grpc.max-inbound-metadata-size=100000\n```\n## 引入后如何使用\n\n### 添加Protobuf如下：\n\n```proto3\nsyntax = \"proto3\";\n\npackage plus.jdk.websocket.protoc;\n\noption java_multiple_files = true;\noption java_package = \"plus.jdk.websocket.broadcast.test.protoc\";\noption java_outer_classname = \"GreeterService\";\noption optimize_for = CODE_SIZE;\n\n\n// The greeting service definition.\nservice Greeter {\n    // Sends a greeting\n    rpc SayHello (HelloRequest) returns (HelloReply) {}\n\n    rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}\n}\n\n// The request message containing the user's name.\nmessage HelloRequest {\n    string name = 1;\n}\n\n// The response message containing the greetings\nmessage HelloReply {\n    string message = 1;\n}\n```\n\n### 指定全局的 ServiceInterceptor.\n\n你需要实现 `GrpcServiceGlobalInterceptorConfigurer`， 并将其声明为一个bean实例\n\n```java\n\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class GrpcServiceGlobalInterceptorConfigurer implements GrpcServiceInterceptorConfigurer {\n\n    private final RSACipherService rsaCipherService;\n\n    @Override\n    public void configureServerInterceptors(List\u003cServerInterceptor\u003e interceptors) {\n        GrpcAuthServerInterceptor grpcAuthServerInterceptor =\n                new GrpcAuthServerInterceptor(rsaCipherService);\n        interceptors.add(grpcAuthServerInterceptor);\n    }\n}\n```\n\n### 如何根据上述的Protobuf结构定义一个Grpc service\n\n```java\npackage plus.jdk.grpc.test.grpc;\n\nimport io.grpc.stub.StreamObserver;\nimport plus.jdk.grpc.annotation.GrpcService;\nimport plus.jdk.grpc.test.grpc.interceptor.AuthServerInterceptor;\nimport plus.jdk.grpc.test.protoc.GreeterGrpc;\nimport plus.jdk.grpc.test.protoc.HelloReply;\nimport plus.jdk.grpc.test.protoc.HelloRequest;\n\n@GrpcService(interceptors = {AuthServerInterceptor.class})\npublic class GreeterImplService extends GreeterGrpc.GreeterImplBase {\n\n    @Override\n    public void sayHello(HelloRequest request, StreamObserver\u003cHelloReply\u003e responseObserver) {\n        HelloReply reply = HelloReply.newBuilder().setMessage(\"Hello \" + request.getName()).build();\n        responseObserver.onNext(reply);\n        responseObserver.onCompleted();\n    }\n\n    @Override\n    public void sayHelloAgain(HelloRequest request, StreamObserver\u003cHelloReply\u003e responseObserver) {\n        HelloReply reply = HelloReply.newBuilder().setMessage(\"Hello again \" + request.getName()).build();\n        responseObserver.onNext(reply);\n        responseObserver.onCompleted();\n    }\n}\n```\n\n### 如何调用上文中定义的GRPC服务（客户端调用）\n\n\n#### 定义声明一个远端的服务器集群\n\n```bash\n\n# 启动客户端的配置\nplus.jdk.grpc.client.enabled=true\n\n# 指定一个默认的连接地址, 指定后 @GrpcClient 注解就默认使用该值\nplus.jdk.grpc.client.default-service=MyGrpc://grpc-service-prod\n\n# 指定服务的scheme地址\nplus.jdk.grpc.client.resolvers[0].scheme=MyGrpc\n\n# 指定服务的host地址\nplus.jdk.grpc.client.resolvers[0].service-name=grpc-service-prod\n\n# 指定远端的GRPC服务列表\nplus.jdk.grpc.client.resolvers[0].hosts[0]=192.168.1.108:10202\nplus.jdk.grpc.client.resolvers[0].hosts[1]=192.168.1.107:10202\n```\n\n#### 服务注册\n\n当你在使用k8s集群的时候，你的集群信息必须随着节点的启动、销毁执行相应的注册、摘除工作，你可以通过实现`IGrpcServiceRegister`接口来完成这个事情\n\n```java\nimport lombok.AllArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport plus.jdk.etcd.global.EtcdClient;\nimport plus.jdk.grpc.common.IGrpcServiceRegister;\n\n@Slf4j\n@AllArgsConstructor\npublic class GrpcServerServiceRegister implements IGrpcServiceRegister {\n\n    private final EtcdClient etcdClient;\n\n    @Override\n    public void registerServiceNode() {\n        log.info(\"registerServiceNode\");\n    }\n\n    @Override\n    public void updateNodeStatus() {\n        log.info(\"updateNodeStatus\");\n    }\n\n    @Override\n    public void deregisterServiceNode() {\n        log.info(\"deregisterServiceNode\");\n    }\n}\n```\n\n##### 服务注册。从配置配置中心（如zookeeper、etcd、redis）读取集群配置信息\n\n在很多情况下，为了保障服务的高可用性，我们会将集群信息存储在配置中心中统一下发，便于某个节点出现故障或扩容时快速新增节点.\n\n你可以通过实现 `IGrpcServiceRegister` 接口来实现上述功能。下文中将给出一个从redis中读取配置的示例：\n\n```java\n\nimport com.google.gson.Gson;\nimport io.etcd.jetcd.kv.TxnResponse;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.BeansException;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.ApplicationContextAware;\nimport org.springframework.lang.NonNull;\nimport org.springframework.web.context.support.WebApplicationObjectSupport;\nimport plus.jdk.etcd.global.EtcdClient;\nimport plus.jdk.grpc.common.IGrpcServiceRegister;\nimport plus.jdk.grpc.config.GrpcPlusProperties;\nimport plus.jdk.grpc.model.GrpcNameResolverModel;\n\nimport java.net.URI;\nimport java.util.ArrayList;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\n\n@Slf4j\npublic class GrpcServerServiceRegister extends WebApplicationObjectSupport implements IGrpcServiceRegister {\n\n    private final EtcdClient etcdClient;\n\n    private final BrandGrpcServerProperties properties;\n\n    private URI serviceUri;\n\n    private Integer port;\n\n    private String registerPath;\n\n    private final Gson gson;\n\n    public GrpcServerServiceRegister(EtcdClient etcdClient, BrandGrpcServerProperties properties, Gson gson) {\n        this.etcdClient = etcdClient;\n        this.properties = properties;\n        this.gson = gson;\n    }\n\n    protected void initRegisterInfo() {\n        if (this.serviceUri != null) {\n            return;\n        }\n        String serviceName = SpringContext.getProperty(properties.getServiceUriKey(), String.class);\n        assert serviceName != null;\n        this.serviceUri = URI.create(serviceName);\n        this.port = SpringContext.getProperty(properties.getServicePortKey(), Integer.class);\n        this.registerPath = String.join(\"/\", new String[]{\n                properties.getServiceRegisterPath(), serviceUri.getHost(), Helper.getIpAddress()\n        });\n    }\n\n    private GrpcNameResolverModel getGrpcNameResolverModel() {\n        initRegisterInfo();\n        String userIp = Helper.getIpAddress();\n        if(StringUtil.isEmpty(userIp)) {\n            throw new RuntimeException(\"cat not get machine ip address\");\n        }\n        GrpcNameResolverModel resolverModel = new GrpcNameResolverModel();\n        resolverModel.setServiceName(serviceUri.getHost());\n        resolverModel.setScheme(serviceUri.getScheme());\n        resolverModel.setHosts(new ArrayList\u003c\u003e());\n        resolverModel.getHosts().add(String.format(\"%s:%s\", userIp, port));\n        return resolverModel;\n    }\n\n    private Long computeNodeExpire() {\n        GrpcPlusProperties grpcPlusProperties = SpringContext.getBean(GrpcPlusProperties.class);\n        if (grpcPlusProperties == null) {\n            return properties.getNodeExpire();\n        }\n        return grpcPlusProperties.getServiceRegisterInterval().getSeconds() * 2;\n    }\n\n\n    @Override\n    public void registerServiceNode() {\n        try {\n            if(SpringContext.isDevelopment() || Boolean.parseBoolean(System.getProperty(\"grpc.service.not.register\"))) {\n                return;\n            }\n            initRegisterInfo();\n            GrpcNameResolverModel resolverModel = getGrpcNameResolverModel();\n            Long expire = computeNodeExpire();\n            CompletableFuture\u003cTxnResponse\u003e future =\n                    etcdClient.put(registerPath, resolverModel, expire * 2);\n            log.info(\"registerServiceNode success, resolverModel:{}, ret:{}\", resolverModel, future.get());\n        } catch (Exception e) {\n            log.info(\"registerServiceNode failed, msg:{}\", e.getMessage());\n        }\n    }\n\n    @Override\n    public void updateNodeStatus() {\n        registerServiceNode();\n    }\n\n    @Override\n    public void deregisterServiceNode() {\n        etcdClient.delete(registerPath);\n    }\n}\n\n```\n\n另外，你可以通过如下配置来指定集群实例列表同步周期：\n\n\n```bash\n# 指定每15秒刷新一次\nplus.jdk.grpc.client.name-refresh-rate=15\n```\n\n#### 服务发现\n\n在上文中我们通过`etcd`来完成了服务注册，现在来做一下服务发现。服务发现其实是在调用方这里来实现的，例如，调用地址是: `myGrpc://ouer-domain.service`，那么客户端就需要知道这个`scheme`后面对应的集群中的所有实例的ip列表是什么。\n\n这里的机制一共有两种。\n\n- 一种是通过定时扫来更新本地的`name service`名称解析，即不断的定时上`ectd`或其他存储集群配置信息的地方去读取这个信息，比如每3秒去拉取更新一次\n- 定时拉取虽好，但是总有延迟，所以也可以在实现里面通过watch etcd key的方式来订阅集群中节点的变化来实时更新数据\n\n在这个框架中，服务发现需要实现 `INameResolverConfigurer`接口，该接口定义如下：\n\n```java\npublic interface INameResolverConfigurer {\n\n    /**\n     * 刷新对应的uri对应的实例列表, 默认每10秒执行一次， 这里默认实现了定时更新的逻辑\n     * 刷新周期可以通过 plus.jdk.grpc.client.name-refresh-rate=15 配置来指定\n     */\n    List\u003cEquivalentAddressGroup\u003e configurationName(URI targetUri);\n\n    /**\n     * 初始化所有自定义的地址， 可以在这里订阅(watch) ectd、zookeeper等其他配置中心的数据来实时更新name service下的内容\n     */\n    void configureNameResolvers(List\u003cGrpcNameResolverModel\u003e resolverModels);\n}\n```\n\n\n```java\n@Slf4j\npublic class GrpcGlobalNameResolverConfigurer implements INameResolverConfigurer {\n\n    private final BrandGrpcClientProperties properties;\n\n    private final RSACipherService rsaCipherService;\n\n    private final CommonRedisService commonRedisService;\n\n    private final EtcdClient etcdClient;\n\n    private List\u003cGrpcNameResolverModel\u003e grpcNameResolverModels;\n\n    private Watch.Watcher watcher;\n\n    private final ScheduledExecutorService scheduledExecutorService;\n\n    public GrpcGlobalNameResolverConfigurer(BrandGrpcClientProperties properties,\n                                            RSACipherService rsaCipherService,\n                                            CommonRedisService commonRedisService,\n                                            EtcdClient etcdClient) {\n        this.properties = properties;\n        this.rsaCipherService = rsaCipherService;\n        this.commonRedisService = commonRedisService;\n        this.etcdClient = etcdClient;\n        this.scheduledExecutorService = Executors.newScheduledThreadPool(2);\n        this.scheduledExecutorService.scheduleAtFixedRate(this::mergeAndScanService,\n                5, 5, TimeUnit.SECONDS);\n    }\n\n    @Override\n    public List\u003cEquivalentAddressGroup\u003e configurationName(URI targetUri) {\n        HashMap\u003cString, GrpcNameResolverModel\u003e grpcNameResolverMap = CollectionUtil.toHashMap(grpcNameResolverModels, data -\u003e String.format(\"%s://%s\", data.getScheme(), data.getServiceName()));\n        GrpcNameResolverModel nameResolverModel = grpcNameResolverMap.get(targetUri.toString());\n        if (nameResolverModel == null) {\n            return new ArrayList\u003c\u003e();\n        }\n        return nameResolverModel.toEquivalentAddressGroups();\n    }\n\n    @Override\n    public void configureNameResolvers(List\u003cGrpcNameResolverModel\u003e resolverModels) {\n        try {\n            TablePrinter tablePrinter = new TablePrinter();\n            mergeAndScanService();\n            grpcNameResolverModels = mergeAndScanService();\n            resolverModels.addAll(grpcNameResolverModels);\n            tablePrinter.printTable(grpcNameResolverModels, GrpcNameResolverModel.class);\n            log.info(\"configureNameResolvers success, grpcNameResolverModels:{}\", grpcNameResolverModels);\n        } catch (Exception | Error e) {\n            log.info(\"configureNameResolvers failed, msg:{}\", e.getMessage());\n        }\n    }\n\n    private List\u003cGrpcNameResolverModel\u003e mergeAndScanService() {\n        try {\n            String configKey = \"your etcd or zk config path\";\n            KeyValuePair\u003cGrpcNameResolverModel[]\u003e nameResolverModels =\n                    etcdClient.getFirstKV(configKey, GrpcNameResolverModel[].class).get();\n            this.grpcNameResolverModels = Arrays.asList(nameResolverModels.getValue());\n            HashMap\u003cString, GrpcNameResolverModel\u003e grpcNameResolverMap =\n                    CollectionUtil.toHashMap(grpcNameResolverModels, GrpcNameResolverModel::buildScheme);\n            List\u003cGrpcNameResolverModel\u003e resolverModels = new ArrayList\u003c\u003e(grpcNameResolverMap.values());\n            CompletableFuture\u003cList\u003cKeyValuePair\u003cGrpcNameResolverModel\u003e\u003e\u003e future =\n                    etcdClient.scanByPrefix(properties.getServiceRegisterPath(), GrpcNameResolverModel.class);\n            List\u003cKeyValuePair\u003cGrpcNameResolverModel\u003e\u003e keyValuePairs = future.get();\n            for (KeyValuePair\u003cGrpcNameResolverModel\u003e keyValuePair : keyValuePairs) {\n                GrpcNameResolverModel resolverModel = keyValuePair.getValue();\n                if (resolverModel == null) {\n                    continue;\n                }\n                if (grpcNameResolverMap.containsKey(resolverModel.buildScheme())) {\n                    grpcNameResolverMap.get(resolverModel.buildScheme()).getHosts().addAll(resolverModel.getHosts());\n                }\n                resolverModels.add(keyValuePair.getValue());\n            }\n            this.grpcNameResolverModels = resolverModels;\n            return resolverModels;\n        } catch (Exception e) {\n        }\n        return new ArrayList\u003c\u003e();\n    }\n}\n```\n\n#### 指定全局的`GrpcClientInterceptor`\n\n同上文，你需要实现 `GrpcClientInterceptorConfigurer` 方法，添加对应的Interceptor\n\n```java\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class GrpcClientInterceptorGlobalConfigurer implements GrpcClientInterceptorConfigurer {\n    \n\n    @Override\n    public void configureClientInterceptors(List\u003cClientInterceptor\u003e interceptors) {\n        // do something\n        interceptors.add(new GrpcClientRSAInterceptor(rsaCipherService));\n    }\n}\n```\n\n#### 编写代码执行远程调用：\n\n你可以使用 `@GrpcClient` 注解来申明一个Grpc 调用的 client， 示例如下：\n\n```java\nimport io.grpc.ManagedChannelBuilder;\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class GRpcRunner implements ApplicationRunner {\n\n    @Value(\"${plus.jdk.grpc.port}\")\n    private String grpcPort;\n\n    @Resource\n    private GrpcSubClientFactory grpcSubClientFactory;\n\n    @GrpcClient(\"MyGrpc://grpc-service-prod\")\n    private GreeterGrpc.GreeterBlockingStub greeterBlockingStub;\n\n    /**\n     * 这里 @GrpcClient 默认使用 `plus.jdk.grpc.client.default-service` 配置项指定的值\n     */\n    @GrpcClient\n    private GreeterGrpc.GreeterBlockingStub greeterBlockingStubDefault;\n\n    @Override\n    public void run(ApplicationArguments args) throws Exception {\n        HelloRequest request = HelloRequest.newBuilder().setName(\"jdk-plus\").build();\n        HelloReply reply = greeterBlockingStub.sayHello(request);\n        log.info(\"sayHello data:{}, receive:{}\", request, reply);\n        reply = blockingStub.sayHelloAgain(request);\n        log.info(\"sayHelloAgain data:{}, receive:{}\", request, reply);\n        TimeUnit.SECONDS.sleep(1);\n    }\n}\n```\n\n### 关于protobuf文件的编译以及打包\n\n此处强烈推荐 `[protobuf-maven-plugin](https://github.com/xolstice/protobuf-maven-plugin/)`,\n\n对应的该组件可以直接在编译时自动构建生成protobuf对应的编译后的java代码，详细的使用方法请参见：`[usage](https://www.xolstice.org/protobuf-maven-plugin/usage.html)`","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjdk-plus%2Fspring-boot-starter-grpc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjdk-plus%2Fspring-boot-starter-grpc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjdk-plus%2Fspring-boot-starter-grpc/lists"}