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 项目,下面三个子模块:apiproducerconsumer。结构如下:

my-grpc-app (父工程)

api 模块
(proto + 生成代码 + 打包 jar)

producer 模块
(gRPC 服务端 + 业务逻辑)

consumer 模块
(HTTP 入口 + gRPC 客户端)

实际目录树:

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-bomprotobuf-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/javagrpc-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 包内包含了编译好的 GreetingServiceGrpcHelloRequestHelloResponse 等类。后续 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();
    }
}

@GrpcServicenet.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. 运行与测试

  1. 先启动 producer:在 IDEA 中运行 GrpcServerApplication,或执行 mvn spring-boot:run -pl producer。确认日志中显示 gRPC server 启动在 9090 端口。

  2. 再启动 consumer:运行 ConsumerApplication,或 mvn spring-boot:run -pl consumer

  3. 调用 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.addressgrpc.client.*.address 中精确配置 IP,或使用 0.0.0.0 监听所有接口(注意安全)。

Q2: 如何启用 TLS 加密?
A: 将 negotiationType 改为 TLS,并配置证书路径,官方文档有详细说明。本文使用 plaintext 仅为快速演示。

Q3: 如何监控或添加拦截器?
A: net.devh 库支持通过 Spring 的 GlobalServerInterceptorGlobalClientInterceptor 注解添加自定义拦截器,用于日志、认证等。

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 的组合开发!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐