目录

创建maven父工程spring-boot-grpc

创建模块spring-boot-grpc-lib

创建模块local-server(gRPC服务端)

创建模块local-client(gRPC客户端)


创建maven父工程spring-boot-grpc

创建springboot项目,勾选springboot-web即可

<?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>
    <modules>
        <module>spring-boot-grpc-lib</module>
        <module>local-server</module>
        <module>local-client</module>
    </modules>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.chenj</groupId>
    <artifactId>spring-boot-grpc</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-grpc</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <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>

    </dependencies>
<!--    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>-->

</project>

创建模块spring-boot-grpc-lib

在父工程spring-boot-grpc下新建maven模块,名为spring-boot-grpc-lib

<?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.chenj</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>
                    <protocArtifact>com.google.protobuf:protoc:3.17.3:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.37.0:exe:${os.detected.classifier}</pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>


</project>

在spring-boot-grpc-lib模块的src/main/proto目录下新增名为helloworld.proto的文件,这里面定义了一个gRPC服务,里面含有一个接口,并且还有这个接口的入参和返回结果的定义:

syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.chenj.grpc.lib";
option java_outer_classname = "HelloWorldProto";

// The greeting service definition.
service Simple {
    // Sends a greeting
    rpc SayHello (HelloRequest) returns (HelloReply) {
    }
}

// The request message containing the user's name.
message HelloRequest {
    string name = 1;
}

// The response message containing the greetings
message HelloReply {
    string message = 1;
}

proto文件已经做好,接下来要根据这个文件来生成Java代码

springboot 配置使用grpc springboot整合grpc_spring boot

SimpleGrpc里面有抽象类SimpleImplBase,制作gRPC服务的时候需要继承该类,另外,如果您要远程调用gRPC的sayHello接口,就会用到SimpleGrpc类中的SimpleStub类,其余的HelloReply、HelloRequest这些则是入参和返回的数据结构定义

创建模块local-server(gRPC服务端)

在父工程下面新建名为local-server的springboot模块,

gRPC 服务端使用一下命令添加 Maven 依赖项

<dependency>
  <groupId>net.devh</groupId>
  <artifactId>grpc-server-spring-boot-starter</artifactId>
  <version>2.12.0.RELEASE</version>
</dependency>

依赖上面的spring-boot-grpc-lib模块

<dependency>
			<groupId>com.chenj</groupId>
			<artifactId>spring-boot-grpc-lib</artifactId>
			<version>0.0.1-SNAPSHOT</version>
</dependency>

完整的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>
		<artifactId>spring-boot-grpc</artifactId>
		<groupId>com.chenj</groupId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
	<groupId>com.chenj</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.chenj</groupId>
			<artifactId>spring-boot-grpc-lib</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</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>

springboot应用,配置文件内容如下:

spring:
  application:
    name: spring-boot-grpc-server
# gRPC有关的配置,这里只需要配置服务端口号
grpc:
  server:
    port: 9898
server:
  port: 8080

新建拦截类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();
    }
}

应用启动类很简单:

package com.chenj.springbootgrpcserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootGrpcServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootGrpcServerApplication.class, args);
    }

}

接下来是最重要的service类,gRPC服务在此处对外暴露出去,完整代码如下

package com.chenj.springbootgrpcserver.service;


import com.chenj.grpc.lib.HelloReply;
import com.chenj.grpc.lib.HelloRequest;
import com.chenj.grpc.lib.SimpleGrpc;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;

import java.util.Date;


@GrpcService
public class GrpcServerService extends SimpleGrpc.SimpleImplBase {

    @Override
    public void sayHello(HelloRequest request,
                         StreamObserver<HelloReply> responseObserver) {
        HelloReply reply = HelloReply.newBuilder().setMessage("你好, " + request.getName() + ", " + new Date()).build();
        responseObserver.onNext(reply);
        responseObserver.onCompleted();
    }
}

上述GrpcServerService.java中有几处需要注意:

是使用@GrpcService注解,再继承SimpleImplBase,这样就可以借助grpc-server-spring-boot-starter库将sayHello暴露为gRPC服务;

SimpleImplBase是前文中根据proto自动生成的java代码,在spring-boot-grpc-lib模块中;

sayHello方法中处理完毕业务逻辑后,调用HelloReply.onNext方法填入返回内容;

调用HelloReply.onCompleted方法表示本次gRPC服务完成;

至此,gRPC服务端编码就完成了,咱们接着开始客户端开发

创建模块local-client(gRPC客户端)

在父工程grpc-turtorials下面新建名为local-client的模块

gRPC客户端使用一下命令添加 Maven 依赖项:

<dependency>
  <groupId>net.devh</groupId>
  <artifactId>grpc-client-spring-boot-starter</artifactId>
  <version>2.12.0.RELEASE</version>
</dependency>

依赖上面的spring-boot-grpc-lib模块

<dependency>
			<groupId>com.chenj</groupId>
			<artifactId>spring-boot-grpc-lib</artifactId>
			<version>0.0.1-SNAPSHOT</version>
</dependency>

完整的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>
        <artifactId>spring-boot-grpc</artifactId>
        <groupId>com.chenj</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <groupId>com.chenj</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.chenj</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>

应用配置文件src/main/resources/application.yml,注意address的值就是gRPC服务端的信息,我这里local-server和local-client在同一台电脑上运行,请您根据自己情况来设置:

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();
    }
}

启动类:

package com.chenj.springbootgrpcclient;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootGrpcClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootGrpcClientApplication.class, args);
    }

}

接下来是最重要的服务类GrpcClientService

package com.chenj.springbootgrpcclient.service;

import com.chenj.grpc.lib.HelloReply;
import com.chenj.grpc.lib.HelloRequest;
import com.chenj.grpc.lib.SimpleGrpc;
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 SimpleGrpc.SimpleBlockingStub simpleStub;

    public String sendMessage(final String name) {
        try {
            final HelloReply response = this.simpleStub.sayHello(HelloRequest.newBuilder().setName(name).build());
            return response.getMessage();
        } catch (final StatusRuntimeException e) {
            return "FAILED with " + e.getStatus().getCode().name();
        }
    }
}

上述GrpcClientService类有几处要注意的地方:

用@Service将GrpcClientService注册为spring的普通bean实例;

用@GrpcClient修饰SimpleBlockingStub,这样就可以通过grpc-client-spring-boot-starter库发起gRPC调用,被调用的服务端信息来自名为local-grpc-server的配置;

SimpleBlockingStub来自前文中根据helloworld.proto生成的java代码;

SimpleBlockingStub.sayHello方法会远程调用local-server应用的gRPC服务;

为了验证gRPC服务调用能否成功,再新增个web接口,接口内部会调用GrpcClientService.sendMessage,这样咱们通过浏览器就能验证gRPC服务是否调用成功了:

package com.chenj.springbootgrpcclient.controller;


import com.chenj.springbootgrpcclient.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("/")
    public String printMessage(@RequestParam(defaultValue = "will") String name) {
        return grpcClientService.sendMessage(name);
    }
}