创建maven父工程:spring-boot-grpc
父工程删去src目录,具体 maven 依赖如下所示:
<?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>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.10</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>spring-boot-grpc</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-grpc</name>
<description>Demo project for Spring Boot</description>
<modules>
<module>spring-boot-grpc-lib</module>
<module>local-server</module>
</modules>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- web 依赖,子模块会继承这些依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 单元测试依赖,子模块会继承这些依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Lombok引入,子模块会继承这些依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<!-- <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>-->
</project>
创建公用子模块:spring-boot-grpc-lib
该模块的 maven 依赖如下所示:该模块打包后会被其他模块引用,且该maven中配置了proto文件生成java文件的插件属性。
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-boot-grpc</artifactId>
<groupId>com.example</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-boot-grpc-lib</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.37.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.37.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.37.0</version>
</dependency>
<dependency> <!-- necessary for Java 9+ -->
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<version>6.0.53</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.2</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<!-- 指定Protobuf编译器protoc具体版本,用于生成Java消息对象 -->
<protocArtifact>com.google.protobuf:protoc:3.17.3:exe:${os.detected.classifier}</protocArtifact>
<!-- 指定protoc的插件Id -->
<pluginId>grpc-java</pluginId>
<!-- 指定生成Java代码的具体插件版本,用于生成Java接口服务 -->
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.37.0:exe:${os.detected.classifier}</pluginArtifact>
<!-- proto文件所在目录 -->
<protoSourceRoot>src/main/proto</protoSourceRoot>
<!-- Java类输出目录 -->
<outputDirectory>src/main/java</outputDirectory>
<!-- 是否需要清理输出目录 -->
<clearOutputDirectory>false</clearOutputDirectory>
</configuration>
<executions>
<execution>
<!-- goals目标说明 -->
<goals>
<!-- 编译消息对象 -->
<goal>compile</goal>
<!-- 依赖上一步生成的消息对象,生成接口服务 -->
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
在spring-boot-grpc-lib模块的src/main/proto目录下新增名为test01.proto的文件,这里面定义了一个grpc服务,里面含有一个接口,并且还有这个接口的入参和返回结果的定义,该文件的示例如下所示:
// 文件的第一行指定您使用的是proto3语法
syntax = "proto3";
// 生成类的包名
option java_multiple_files = true;
// 生成类的文件名,否则默认生成的类名为proto文件名的驼峰命名
option java_package = "com.chenj.grpc.lib";
// 定义的所有消息、枚举和服务生成对应的多个类文件,而不是以内部类的形式出现
option java_outer_classname = "Test01Proto";
// The greeting service definition.(greeting服务定义)
// 生成 Service01Grpc类、以及该类的方法
service Service01 {
// Sends a greeting.
// 生成method01方法(格式为"方法名 请求参数 返回参数")
rpc method01 (Method01Request) returns (Method01Reply) {
}
// Sends a greeting.
// 生成method02方法(格式为"方法名 请求参数 返回参数")
rpc method02 (Method02Request) returns (Method02Reply) {
}
}
// 生成方法请求类、以及该类的属性
message Method01Request {
string name = 1;
}
// 生成方法响应类、以及该类的属性
message Method01Reply {
string message = 1;
}
// 生成方法请求类、以及该类的属性
message Method02Request {
string param1 = 1;
string param2 = 2;
}
// 生成方法响应类、以及该类的属性
message Method02Reply {
string param1 = 1;
string param2 = 2;
}
proto文件已经做好,接下来要根据这个文件来生成Java代码;操作方式如下:
点击install打成jar包,这样,一则根据spring-boot-grpc-lib模块的maven依赖配置中的<plugin>插件配置生成java代码;二是可以使其他模块在maven中添加该模块的依赖包。
创建子模块:local-server(gRPC服务端)
在父工程下面新建名为local-server的springboot模块,该模块的maven依赖如下所示:
<?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>
<artifactId>spring-boot-grpc</artifactId>
<groupId>com.example</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.example</groupId>
<artifactId>local-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>local-server</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>2.12.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>spring-boot-grpc-lib</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--Lombok引入-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>2.7.10</version>
</dependency>
</dependencies>
<!-- <build>-->
<!-- <plugins>-->
<!-- <plugin>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-maven-plugin</artifactId>-->
<!-- </plugin>-->
<!-- </plugins>-->
<!-- </build>-->
</project>
local-server 模块的application.yml文件配置如下:
server:
port: 8080
spring:
application:
name: spring-boot-grpc-server
# gRPC有关的配置,这里只需要配置服务端口号
grpc:
server:
port: 9898
在 local-server 模块新建拦截类LogGrpcInterceptor.java,每当gRPC请求到来后该类会先执行,这里是将方法名字在日志中打印出来,您可以对请求响应做更详细的处理:
package com.chenj.springbootgrpcserver.interceptor;
import io.grpc.Metadata;
import io.grpc.ServerCall;
import io.grpc.ServerCallHandler;
import io.grpc.ServerInterceptor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class LogGrpcInterceptor implements ServerInterceptor {
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall, Metadata metadata,
ServerCallHandler<ReqT, RespT> serverCallHandler) {
log.info(serverCall.getMethodDescriptor().getFullMethodName());
return serverCallHandler.startCall(serverCall, metadata);
}
}
为了让LogGrpcInterceptor可以在gRPC请求到来时被执行,需要做相应的配置,如下所示,在普通的bean的配置中添加注解即可:
package com.chenj.springbootgrpcserver.config;
import com.chenj.springbootgrpcserver.interceptor.LogGrpcInterceptor;
import io.grpc.ServerInterceptor;
import net.devh.boot.grpc.server.interceptor.GrpcGlobalServerInterceptor;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class GlobalInterceptorConfiguration {
@GrpcGlobalServerInterceptor
ServerInterceptor logServerInterceptor() {
return new LogGrpcInterceptor();
}
}
接下来是最重要的service类,gRPC服务在此处对外暴露出去,完整代码如下:
package com.example.service;
import com.chenj.grpc.lib.*;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;
import java.util.Date;
@GrpcService
public class GrpcTest01Service extends Service01Grpc.Service01ImplBase {
@Override
public void method01(Method01Request request,
StreamObserver<Method01Reply> responseObserver) {
Method01Reply reply = Method01Reply.newBuilder()
.setMessage("name:" + request.getName() + ", " + new Date())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
@Override
public void method02(Method02Request request,
StreamObserver<Method02Reply> responseObserver) {
Method02Reply reply = Method02Reply.newBuilder()
.setParam1("参数1:" + request.getParam1())
.setParam2("参数2:" + request.getParam2())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
至此,grpc服务端编码完成。
创建子模块:local-client(gRPC客户端)
在父工程spring-boot-grpc下面新建名为local-client的模块,该模块的maven依赖如下所示:
<?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>
<artifactId>spring-boot-grpc</artifactId>
<groupId>com.example</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.example</groupId>
<artifactId>local-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>local-client</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<version>2.12.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>spring-boot-grpc-lib</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
local-client 模块的application.yml文件配置如下:
server:
port: 8088
spring:
application:
name: local-client
grpc:
client:
# gRPC配置的名字,GrpcClient注解会用到
local-grpc-server:
# gRPC服务端地址
address: 'static://127.0.0.1:9898'
enableKeepAlive: true
keepAliveWithoutCalls: true
negotiationType: plaintext
新建拦截类LogGrpcInterceptor,与服务端的拦截类差不多,不过实现的接口不同:
package com.chenj.springbootgrpcclient.interceptor;
import io.grpc.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogGrpcInterceptor implements ClientInterceptor {
private static final Logger log = LoggerFactory.getLogger(LogGrpcInterceptor.class);
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method,
CallOptions callOptions, Channel next) {
log.info(method.getFullMethodName());
return next.newCall(method, callOptions);
}
}
为了让拦截类能够正常工作,即发起gRPC请求的时候被执行,需要新增一个配置类:
package com.chenj.springbootgrpcclient.config;
import com.chenj.springbootgrpcclient.interceptor.LogGrpcInterceptor;
import io.grpc.ClientInterceptor;
import net.devh.boot.grpc.client.interceptor.GrpcGlobalClientInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
@Order(Ordered.LOWEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
public class GlobalClientInterceptorConfiguration {
@GrpcGlobalClientInterceptor
ClientInterceptor logClientInterceptor() {
return new LogGrpcInterceptor();
}
}
接下来是最重要的服务类GrpcClientService:
package com.example.service;
import com.chenj.grpc.lib.*;
import io.grpc.StatusRuntimeException;
import net.devh.boot.grpc.client.inject.GrpcClient;
import org.springframework.stereotype.Service;
@Service
public class GrpcClientService {
@GrpcClient("local-grpc-server")
private Service01Grpc.Service01BlockingStub service01Stub;
public String method01(final String name) {
try {
final Method01Reply response = this.service01Stub.method01(
Method01Request.newBuilder().setName(name).build());
return response.getMessage();
} catch (final StatusRuntimeException e) {
return "FAILED with " + e.getStatus().getCode().name();
}
}
public String method02(final String param1,
final String param2) {
try {
final Method02Reply response = this.service01Stub.method02(
Method02Request.newBuilder()
.setParam1(param1)
.setParam2(param2)
.build());
return response.getParam1();
} catch (final StatusRuntimeException e) {
return "FAILED with " + e.getStatus().getCode().name();
}
}
}
为了验证gRPC服务调用能否成功,再新增个web接口,接口内部会调用GrpcClientService.sendMessage,这样就可通过浏览器或postman就能验证gRPC服务是否调用成功了:
package com.example.controller;
import com.example.service.GrpcClientService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GrpcClientController {
@Autowired
private GrpcClientService grpcClientService;
@RequestMapping("/test02")
public String test02(@RequestParam(defaultValue = "will") String name) {
return grpcClientService.method01(name);
}
@RequestMapping("/test03")
public String test03(@RequestParam(defaultValue = "param1") String param1,
@RequestParam(defaultValue = "param2") String param2) {
return grpcClientService.method02(param1, param2);
}
}
参考:3小时快速入门Java版gRPC系列(九)-使用spring boot集成grpc - 知乎 (zhihu.com)