grpc与业务代码隔离:java实战
1. 引言
在现代微服务架构中,gRPC 凭借其高性能、跨语言和强大的接口定义能力,成为服务间通信的热门选择。但在实际 Java 项目中,往往希望对外暴露 HTTP 接口,而内部服务调用则使用 gRPC。本文将以一个真实的多模块项目为例,讲解如何用 Spring Boot + gRPC 搭建「HTTP 入口 + gRPC 内部调用」的开发模板。
我们将构建一个包含三个模块的 Maven 工程:
- api:只负责定义 Protocol Buffers,并通过插件生成 Java 客户端和服务端代码,最终打包成 jar。
- producer:gRPC 服务提供者,同时也是一个 Spring Boot 应用,通过配置文件指定 gRPC 端口。
- consumer:gRPC 服务消费者/调用者,对外暴露 HTTP 接口,内部通过 gRPC 调用 producer。
gRPC 通信统一使用 127.0.0.1,非常适合本地开发、演示以及作为项目骨架。
读完本文,你将能够:
- 在 IDEA 中创建多模块 Maven 工程
- 使用 protobuf-maven-plugin 生成 gRPC 代码
- 编写 gRPC 服务端和客户端
- 用 Spring Boot 配置并启动 gRPC 服务
- 从 HTTP 接口转发至 gRPC 调用
- 接上篇git submodule 教程把proto module 作为单独git仓库,多个工程引用,就可实现跨项目协作api和api与业务代码隔离
2. 环境准备
工具及版本:
- JDK 17+
- Maven 3.8+
- IDEA(社区版即可)
- Spring Boot 3.x
- gRPC & Protobuf 插件
最终技术栈:
- Spring Boot Starter Web(HTTP 入口)
- gRPC Server & Client(内部 RPC)
- Protocol Buffers 3
- Maven 多模块管理
3. 项目整体结构
整个工程为一个 Maven Parent 项目,下面三个子模块:api、producer、consumer。结构如下:
实际目录树:
my-grpc-app
├── pom.xml # 父 POM,管理依赖和插件
├── api/
│ ├── pom.xml # 只配置 protobuf 插件
│ └── src/main/proto/
│ └── hello.proto # 服务定义
├── producer/
│ ├── pom.xml # 依赖 api 模块,配置 grpc-server-spring-boot-starter
│ └── src/main/java/com/chxus/
│ ├── GrpcServerApplication.java
│ ├── service/
│ │ └── HelloServiceImpl.java
│ └── configuration/
│ └── GrpcServerConfig.java # 可省略,用 application.yml 配置
├── consumer/
│ ├── pom.xml # 依赖 api 模块,配置 grpc-client-spring-boot-starter
│ └── src/main/java/com/chxus/
│ ├── ConsumerApplication.java
│ ├── controller/
│ │ └── HelloController.java
│ └── client/
│ └── HelloGrpcClient.java
图中每个模块的 pom.xml 是关键,下面会逐步讲解。
4. 父工程 POM 配置
首先创建父工程 my-grpc-app,只保留 pom.xml,打包方式声明为 pom,管理 Spring Boot 和 gRPC 的版本。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/>
</parent>
<groupId>com.chxus</groupId>
<artifactId>my-grpc-app</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>my-grpc-app</name>
<modules>
<module>api</module>
<module>producer</module>
<module>consumer</module>
</modules>
<properties>
<java.version>17</java.version>
<grpc.version>1.62.2</grpc.version>
<protobuf.version>3.25.3</protobuf.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- gRPC & Protobuf BOM -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-bom</artifactId>
<version>${grpc.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-bom</artifactId>
<version>${protobuf.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!-- 跳过父工程打包 -->
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>
这里使用了 grpc-bom 和 protobuf-bom 统一管理版本,后续子模块只需引入具体依赖而不写版本号。
5. api 模块:Proto 定义与代码生成
api 模块是整个工程的“契约”中心。我们只放置 .proto 文件,并在打包时通过插件生成 Java 的客户端和服务端代码,最后生成的 jar 包里就包含了这些 gRPC 代码,供 producer 和 consumer 直接依赖。
5.1 api/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.chxus</groupId>
<artifactId>my-grpc-app</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>api</artifactId>
<packaging>jar</packaging>
<name>api</name>
<dependencies>
<!-- 两个运行时依赖,用于生成代码的编译 -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</dependency>
<!-- 如果使用 Kotlin 或其它语言可忽略 -->
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.1</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
<!-- 重要:生成客户端和服务端代码 -->
<clearOutputDirectory>false</clearOutputDirectory>
<outputDirectory>${project.build.directory}/generated-sources/protobuf/java</outputDirectory>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- 让 IDEA 识别生成的源代码目录 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${project.build.directory}/generated-sources/protobuf/java</source>
<source>${project.build.directory}/generated-sources/protobuf/grpc-java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
重点解释:
protobuf-maven-plugin会根据src/main/proto/下的.proto文件生成消息类和服务接口。- 生成后的代码位于
target/generated-sources/protobuf/java和grpc-java目录。 build-helper-maven-plugin将它们加入编译路径,确保 IDEA 能识别。
5.2 编写 Proto 文件
在 api/src/main/proto/hello.proto 中写入:
syntax = "proto3";
package com.chxus;
option java_multiple_files = true;
option java_package = "com.chxus";
option java_outer_classname = "HelloProto";
// 定义请求与响应
message HelloRequest {
string name = 1;
}
message HelloResponse {
string message = 1;
}
// 定义服务
service GreetingService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
java_package设为com.chxus,这样所有生成的 Java 类都在该包下。java_multiple_files = true会让每个消息生成单独的 Java 文件,便于使用。
5.3 打包验证
在命令行进入 api 目录执行 mvn clean install,或直接在父工程下执行 mvn clean install -pl api。完成后,本地仓库中会生成 api-1.0.0-SNAPSHOT.jar。该 jar 包内包含了编译好的 GreetingServiceGrpc、HelloRequest、HelloResponse 等类。后续 producer 和 consumer 直接依赖即可,无需重复生成代码。
6. producer 模块:gRPC 服务端
producer 模块既是 Spring Boot 应用,也是一个 gRPC 服务提供者。它需要依赖 api 模块和 grpc-server-spring-boot-starter。
6.1 producer/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.chxus</groupId>
<artifactId>my-grpc-app</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>producer</artifactId>
<packaging>jar</packaging>
<name>producer</name>
<dependencies>
<!-- 依赖 api 模块(包含 gRPC 代码) -->
<dependency>
<groupId>com.chxus</groupId>
<artifactId>api</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<!-- Spring Boot Starter(如果不需要 HTTP 端口可省略,但这里保留) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- gRPC Server Starter,底层 Netty -->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>3.0.0-RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
我们使用 net.devh 社区维护的 Spring Boot gRPC 集成库,它通过注解和自动配置极大简化了 gRPC 服务的发布。
6.2 实现 gRPC 服务
在 producer 模块的 src/main/java/com/chxus/ 下创建 GrpcServerApplication.java 作为启动类,并实现服务逻辑。
GrpcServerApplication.java
package com.chxus;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GrpcServerApplication {
public static void main(String[] args) {
SpringApplication.run(GrpcServerApplication.class, args);
}
}
HelloServiceImpl.java(实现 proto 生成的 GreetingServiceGrpc.GreetingServiceImplBase)
package com.chxus.service;
import com.chxus.GreetingServiceGrpc;
import com.chxus.HelloRequest;
import com.chxus.HelloResponse;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;
@GrpcService
public class HelloServiceImpl extends GreetingServiceGrpc.GreetingServiceImplBase {
@Override
public void sayHello(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
String name = request.getName();
String message = "Hello, " + name + "! This message comes from gRPC server.";
HelloResponse response = HelloResponse.newBuilder()
.setMessage(message)
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
@GrpcService 是 net.devh 提供的注解,它会让 Spring Boot 自动将该服务类注册到 gRPC 服务器中。
6.3 配置文件
src/main/resources/application.yml
server:
port: 8080 # HTTP 端口(可选,如果不需要 Web 功能可设为 0)
grpc:
server:
port: 9090 # gRPC 服务端口
address: 127.0.0.1 # 只监听本地
若不使用 @GrpcService,也可手动配置,但这种方式最简洁。
启动 producer 模块,控制台应输出类似 gRPC Server started, listening on address: /127.0.0.1, port: 9090 的日志,表示服务正常运行。
7. consumer 模块:HTTP 入口 & gRPC 客户端
consumer 模块对外提供 HTTP 接口,并通过 gRPC 调用 producer 的服务。它需要依赖 api 模块和 grpc-client-spring-boot-starter。
7.1 consumer/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.chxus</groupId>
<artifactId>my-grpc-app</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>consumer</artifactId>
<packaging>jar</packaging>
<name>consumer</name>
<dependencies>
<!-- api 模块 -->
<dependency>
<groupId>com.chxus</groupId>
<artifactId>api</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<!-- Spring Boot Web(提供 HTTP 接口) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- gRPC Client Starter -->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<version>3.0.0-RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
7.2 编写 gRPC 客户端
创建一个配置类或直接使用 @GrpcClient 注解注入服务桩代码。推荐后者。
HelloGrpcClient.java(可封装为一个 Service)
package com.chxus.client;
import com.chxus.GreetingServiceGrpc;
import com.chxus.HelloRequest;
import com.chxus.HelloResponse;
import net.devh.boot.grpc.client.inject.GrpcClient;
import org.springframework.stereotype.Service;
@Service
public class HelloGrpcClient {
@GrpcClient("greeting-service") // 名称与配置中的 client 名称对应
private GreetingServiceGrpc.GreetingServiceBlockingStub greetingStub;
public String sayHello(String name) {
HelloRequest request = HelloRequest.newBuilder()
.setName(name)
.build();
HelloResponse response = greetingStub.sayHello(request);
return response.getMessage();
}
}
7.3 HTTP 控制器
package com.chxus.controller;
import com.chxus.client.HelloGrpcClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@Autowired
private HelloGrpcClient helloGrpcClient;
@GetMapping("/hello")
public String hello(@RequestParam(defaultValue = "world") String name) {
return helloGrpcClient.sayHello(name);
}
}
7.4 配置文件
src/main/resources/application.yml
server:
port: 8081 # consumer HTTP 端口,避免与 producer 冲突
grpc:
client:
greeting-service:
address: static://127.0.0.1:9090
negotiationType: plaintext # 开发环境使用明文传输
greeting-service是客户端命名,对应@GrpcClient注解中的值。static://前缀表示直接指定 gRPC 服务端地址,也可使用 DNS、Zookeeper 等发现机制。plaintext允许不使用 TLS,适合本地开发。
7.5 启动类
package com.chxus;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
8. 运行与测试
-
先启动 producer:在 IDEA 中运行
GrpcServerApplication,或执行mvn spring-boot:run -pl producer。确认日志中显示 gRPC server 启动在 9090 端口。 -
再启动 consumer:运行
ConsumerApplication,或mvn spring-boot:run -pl consumer。 -
调用 HTTP 接口:浏览器访问
http://localhost:8081/hello?name=Chxus,预期返回:Hello, Chxus! This message comes from gRPC server.
若出现连接异常,请检查:
- 两个应用的端口是否冲突(consumer 8081, producer 8080/9090)
application.yml中 gRPC 地址是否正确- 是否先启动了 producer
9. 常见问题与优化建议
Q1: 如何在多网卡或 Docker 环境下指定监听地址?
A: 在 grpc.server.address 和 grpc.client.*.address 中精确配置 IP,或使用 0.0.0.0 监听所有接口(注意安全)。
Q2: 如何启用 TLS 加密?
A: 将 negotiationType 改为 TLS,并配置证书路径,官方文档有详细说明。本文使用 plaintext 仅为快速演示。
Q3: 如何监控或添加拦截器?
A: net.devh 库支持通过 Spring 的 GlobalServerInterceptor 和 GlobalClientInterceptor 注解添加自定义拦截器,用于日志、认证等。
Q4: 能否将 producer 也做成 HTTP 入口?
A: 完全可以,只需添加 spring-boot-starter-web 依赖即可。但本例 producer 只作为 gRPC 服务方,consumer 统一对外提供 HTTP,职责更清晰。
10. 总结
本文从零构建了一个基于 Spring Boot 的 gRPC 多模块工程,核心要点回顾:
- 使用 api 模块集中管理
.proto文件,通过protobuf-maven-plugin生成 Java 代码并打包为 jar,避免代码重复。 - 不同的java工程可以直接使用jar包,避免copy代码导致的接口不一致,当前最合适的方式是使用git submodule,可以跨团队协作api,同时隔离api与业务代码
- producer 使用
@GrpcService声明服务,配置文件指定 gRPC 端口和地址。 - consumer 使用
@GrpcClient注入客户端 Stub,通过 HTTP 接口透明调用远端 gRPC 服务。 - 这种模式将 HTTP 与 gRPC 分离,职责更清晰,易于扩展和部署。
你可以将这套模板直接用于实际项目开发,根据业务需要增加更多的 gRPC 服务和 HTTP 端点。完整演示代码可在本地运行,网络使用 127.0.0.1,开箱即用。
希望本文能够帮助你快速上手 Java 与 gRPC 的组合开发!
更多推荐




所有评论(0)