Spring Boot 4.0 + gRPC 极简集成:4步搞定,比REST还丝滑

关注我的公众号:【编程朝花夕拾】,可获取首发内容。

01 引言

gRPC 是 Google 开源的高性能 RPC 框架,基于 HTTP/2 + Protocol Buffers,序列化效率和传输速度都比传统 REST + JSON 高出一截,在微服务内部通信、移动端调用、跨语言服务等场景下非常吃香。

但传统 gRPC 的 Java 集成姿势比较原始:手写 ServerBuilder、手写 ManagedChannel、手写拦截器链,样板代码一堆。Spring gRPC(1.0.x,2025 年正式发布)的目标就是把这些脏活全包了,让你像写普通 Spring Boot 应用一样写 gRPC。

02 环境准备

Spring gRPC对环境还是有要求的:

  • 最低Java17
  • Spring Framework 7.0.1
  • Spring gRPC 1.0.x使用Spring Boot要求4.0.x

03 Spring gRPC Server

3.1 Maven依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-grpc-server</artifactId>
</dependency>

使用Spring的脚手架,还引入了protobuf-maven-plugin编译插件。

Protobuf 编译插件,让 Maven/Gradle 自动把 .proto 文件编译成 Java 类。

3.2 定义 proto 文件

src/main/proto/hello.proto 创建服务定义(注意把 java_package 换成你自己的包名)。

使用Spring脚手架自动生成proto文件包。

protobuf 复制代码
syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.simonking.ws.boot.grpc.server.proto";
option java_outer_classname = "UserProto";

package user;

// 用户信息服务 - 展示多参数 gRPC 方法
service UserService {
  // 多参数创建用户
  rpc CreateUser (CreateUserRequest) returns (CreateUserResponse);
  // 多参数查询用户
  rpc GetUser (GetUserRequest) returns (GetUserResponse);
}

// ============ 通用消息 ============

// 用户信息
message UserInfo {
  int64 id = 1;
  string username = 2;
  string email = 3;
  int32 age = 4;
  string phone = 5;
  string created_at = 6;
  string updated_at = 7;
}

// ============ 创建用户 (多参数) ============

message CreateUserRequest {
  string username = 1;
  string email = 2;
  int32 age = 3;
  string phone = 4;
  string password = 5;
  string nickname = 6;
}

message CreateUserResponse {
  bool success = 1;
  string message = 2;
  int64 user_id = 3;
}

// ============ 查询用户 ============

message GetUserRequest {
  int64 user_id = 1;
}

message GetUserResponse {
  bool success = 1;
  string message = 2;
  UserInfo user = 3;
}

在这里定义方法,参数以及响应。

字段说明

  • java_package:生成的 Java 类放在哪个包
  • java_multiple_files = true:每个 message 一个独立 .java 文件(推荐)
  • java_outer_classname:容器类名(一般用不到,因为开了 multiple_files)
  • 字段后面的 = 1 是 protobuf 的字段编号,不能改也不能重用,是序列化兼容性的关键

3.3 编译生成stub

执行:

bash 复制代码
./mvnw clean compile

编译完成之后,会在target/generated-sources/protobuf 生成对应的实体和服务:

IntelliJ 用户 注意:右键 target/generated-sources 目录 → Mark Directory asGenerated Sources Root,否则 IDE 识别不到。

3.4 实现方法

继承XxxGrpc.XxxImplBase即可。

java 复制代码
@GrpcService
public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {

    private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
    private static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    // 内存存储模拟数据库
    private final ConcurrentHashMap<Long, UserInfo> userStore = new ConcurrentHashMap<>();
    private final AtomicLong idGenerator = new AtomicLong(1);

    // ===================== 1. 创建用户 =====================

    @Override
    public void createUser(CreateUserRequest request,
                           StreamObserver<CreateUserResponse> responseObserver) {
        long userId = idGenerator.getAndIncrement();
        String now = LocalDateTime.now().format(DTF);

        UserInfo user = UserInfo.newBuilder()
                .setId(userId)
                .setUsername(request.getUsername())
                .setEmail(request.getEmail())
                .setAge(request.getAge())
                .setPhone(request.getPhone())
                .setCreatedAt(now)
                .setUpdatedAt(now)
                .build();

        userStore.put(userId, user);

        CreateUserResponse response = CreateUserResponse.newBuilder()
                .setSuccess(true)
                .setMessage("用户创建成功")
                .setUserId(userId)
                .build();

        responseObserver.onNext(response);
        responseObserver.onCompleted();
        log.info("===== createUser 响应: userId={} =====", userId);
    }

    // ===================== 2. 查询用户 =====================

    @Override
    public void getUser(GetUserRequest request,
                        StreamObserver<GetUserResponse> responseObserver) {
        log.info("===== getUser: userId={} =====", request.getUserId());

        UserInfo user = userStore.get(request.getUserId());
        if (user == null) {
            GetUserResponse response = GetUserResponse.newBuilder()
                    .setSuccess(false)
                    .setMessage("用户不存在: " + request.getUserId())
                    .build();
            responseObserver.onNext(response);
            responseObserver.onCompleted();
            return;
        }

        GetUserResponse response = GetUserResponse.newBuilder()
                .setSuccess(true)
                .setMessage("查询成功")
                .setUser(user)
                .build();

        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

注意实现类上的注解:org.springframework.grpc.server.service.GrpcService

3.5 启动类

启动类不需要额外的编写,但是启动类帮我们做了三件事:

  • 启动 Netty-based gRPC Server
  • 监听 spring.grpc.server.port(默认 9090
  • 把所有 BindableService Bean 注册上去

04 Spring gRPC Client

客户端的编写和服务端差不多。

4.1 Maven依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-grpc-client</artifactId>
</dependency>

<build>
    <plugins>
        <plugin>
            <groupId>io.github.ascopes</groupId>
            <artifactId>protobuf-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

3.2 定义 proto 文件

同服务端

3.3 编译生成stub

同服务端

3.4 配置客户端

由于gRPC基于Netty,所以需要配置通道信息。

java 复制代码
@Configuration
public class GrpcClientConfig {

    @Value("${grpc.client.host:localhost}")
    private String host;

    @Value("${grpc.client.port:9090}")
    private int port;

    @Bean(destroyMethod = "shutdown")
    public ManagedChannel grpcChannel() {
        return ManagedChannelBuilder.forAddress(host, port)
                .usePlaintext()
                .build();
    }

    @Bean
    public UserServiceGrpc.UserServiceBlockingStub blockingStub(ManagedChannel channel) {
        return UserServiceGrpc.newBlockingStub(channel);
    }
}

3.5 调用方法

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    private static final Logger log = LoggerFactory.getLogger(UserController.class);

    @Autowired
    private UserServiceBlockingStub blockingStub;

    // ===================== 1. 创建用户 =====================

    @PostMapping("/create")
    public Map<String, Object> createUser() {
        CreateUserRequest request = CreateUserRequest.newBuilder()
                .setUsername("test_user")
                .setEmail("test@example.com")
                .setAge(25)
                .setPhone("13800138000")
                .setPassword("123456")
                .setNickname("测试用户")
                .build();

        CreateUserResponse resp = blockingStub.createUser(request);
        log.info("createUser -> userId={}", resp.getUserId());
        return Map.of("success", resp.getSuccess(), "message", resp.getMessage(), "userId", resp.getUserId());
    }

    // ===================== 2. 查询用户 =====================

    @GetMapping("/{userId}")
    public Map<String, Object> getUser(@PathVariable long userId) {
        GetUserRequest request = GetUserRequest.newBuilder()
                .setUserId(userId)
                .build();
        GetUserResponse resp = blockingStub.getUser(request);

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("success", resp.getSuccess());
        result.put("message", resp.getMessage());
        if (resp.hasUser()) {
            result.put("user", toUserMap(resp.getUser()));
        }
        return result;
    }

    // ===================== 工具方法 =====================

    private Map<String, Object> toUserMap(UserInfo user) {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("id", user.getId());
        map.put("username", user.getUsername());
        map.put("email", user.getEmail());
        map.put("age", user.getAge());
        map.put("phone", user.getPhone());
        map.put("createdAt", user.getCreatedAt());
        map.put("updatedAt", user.getUpdatedAt());
        return map;
    }
}

3.6 测试

创建用户

查询用户

相关推荐
C++、Java和Python的菜鸟1 小时前
第3章 后端Web基础 (Maven)
java
小小编程路1 小时前
Java super 关键字详解:用法、场景与常见误区
java·开发语言
我登哥MVP1 小时前
走进 Gang of Four 设计模式:外观模式
java·设计模式·外观模式
IT_陈寒1 小时前
Vue的这个响应式陷阱,让我加班到凌晨三点
前端·人工智能·后端
tiancaijiben2 小时前
阿里云SAE应用引擎弹性扩缩容完全指南:从手动调控到智能自动化
java·网络·数据库
增量星球2 小时前
《持续交付2.0系列一》从技术交付到业务引领
java·docker·容器·devops
ch.ju2 小时前
Java Programming Chapter 4——Anonymous inner class
java·开发语言
Mr__Miss2 小时前
Java 随机数生成器选型指南:Random、ThreadLocalRandom 与 SecureRandom
java·开发语言·python
Wang's Blog2 小时前
Go-Zero基础入门5: 探究go-zero如何基于gRPC扩展客户端
开发语言·后端·golang·go-zero