在快速开始中,已经成功地运行了tRPC-Java的基础示例,但这还远远不够。本教程中你将继续学会:
- 如何通过Protocol Buffer定义
tRPC-Java服务 - 如何通过YAML配置
tRPC-Java服务 - 如何用Spring Boot集成
tRPC-Java服务 tRPC-Java具有哪些扩展能力tRPC-Java支持的各种能力
为了实现跨语言的兼容性,tRPC服务依赖Protocol Buffer v3(后文简称protobuf)来定义,protobuf
的详细说明可参考其 Java官方文档 。
tRPC-Java已提供最小服务依赖,在开始自定义服务前,可引入如下依赖:
<dependency>
<groupId>com.tencent.trpc</groupId>
<artifactId>trpc-mini</artifactId>
<version>${tRPC-Java.version}</version>
</dependency>
tRPC-Java仓库中的示例代码已添加该依赖,如继续运行示例代码,可跳过该节。
为了定义一个新服务,首先需要在protobuf中声明它。下面的示例声明了一个名为GreeterService的服务。
service GreeterService {
// ...
}一个服务中有各种各样的方法,它们需要声明在Service的内部。比如下面的示例中,GreeterService声明了一个名为sayHello的方法,它以HelloRequest
作为请求参数,以HelloResponse作为回包参数。
service GreeterService {
rpc sayHello (HelloRequest) returns (HelloResponse) {}
}
message HelloRequest {
// ...
}
message HelloResponse {
// ...
}一个完整的tRPC服务的protobuf示例配置可参考:trpc-java-demo/src/main/proto/demo.proto。
protobuf给出的是一个语言无关的服务定义,定义完成后,可以使用 trpc-maven-plugin插件
来将它生成tRPC-Java项目的桩代码。
桩代码的核心是一个包括@TRpcService和@TRpcMethod注解的服务接口,如:
// TRpcService用于定义服务名,服务名为trpc开头的四段点分格式的字符串。
@TRpcService(name = "trpc.TestApp.TestServer.GreeterService")
public interface GreeterserviceAPI {
// TRpcMethod用于定义接口名。
@TRpcMethod(name = "sayHello")
HelloRequestProtocol.HelloResponse sayHello(RpcContext context,
HelloRequestProtocol.HelloRequest request);
}示例代码可参考trpc-maven-plugin
插件生成的接口:trpc-java-demo/target/generated-sources/trpc/java/com/tencent/trpc/demo/proto/GreeterserviceAPI.java
。
trpc-maven-plugin会同时生成普通RPC接口或异步RPC接口,任选一个即可。
继承生成的服务接口,实现服务端代码:
// 服务端接口实现
public class GreeterServiceImpl implements GreeterserviceAPI {
@Override
public HelloRequestProtocol.HelloResponse sayHello(RpcContext context,
HelloRequestProtocol.HelloRequest request) {
// do your business
return something;
}
}示例代码可参考:trpc-java-demo/src/main/java/com/tencent/trpc/demo/server/impl/GreeterServiceImpl.java。
客户端代码只需要拿到对应服务的服务接口(如GreeterserviceAPI或GreeterserviceAsyncAPI)即可,不需要实现。
在 快速开始 中,演示的是通过代码的方式配置tRPC-Java服务,本教程将演示如何通过YAML来配置tRPC-Java服务。
下面的YAML配置示例中,演示了如何在服务端配置tRPC-Java服务:
server:
app: TestApp # 应用名
server: TestServer # 服务名
local_ip: 127.0.0.1 # 本地IP地址
service: # 服务列表,可定义多个
- name: trpc.TestApp.TestServer.Greeter1 # 服务名,通常用于注册到服务中心
impls: # 服务实现类,提供具体的RPC服务
- com.tencent.trpc.demo.server.impl.GreeterServiceImpl
ip: 127.0.0.1 # 服务监听地址
port: 12321 # 服务监听端口
network: tcp # 网络协议,如tcp或udp
protocol: trpc # 通信协议,默认trpc
serialization: pb # 序列化方式,默认pb
transport: netty # 通信框架,默认netty示例配置可参考:trpc-java-demo/src/main/resources/trpc_java_server.yaml。
下面的YAML配置示例中,演示了如何在客户端配置tRPC-Java服务:
server: # 本地服务信息,主要用于配置RPC调用的主调信息等
app: TestApp # 应用名
server: TestServer # 服务名
local_ip: 127.0.0.1 # 本地IP地址
client: # 客户端配置
service:
- name: trpc.TestApp.TestServer.Greeter1 # 服务名
interface: com.tencent.trpc.demo.proto.GreeterserviceAPI # 服务接口
naming_url: ip://127.0.0.1:12321 # 远程服务地址,这里指通过ip+port的方式访问
network: tcp # 网络协议,如tcp或udp
protocol: trpc # 通信协议,默认trpc
serialization: pb # 序列化方式,默认pb
transport: netty # 通信框架,默认netty示例配置参考:trpc-java-demo/src/main/resources/trpc_java_client.yaml。
通常一个
tRPC-Java服务既可以是服务端,也可以是客户端,上述两种配置可合并定义到同一个配置文件中。
更多的配置项说明可参考 YAML配置文档
服务端YAML配置文件定义好后,可使用如下方式启动服务端:
class Server {
public static void main(String[] args) {
TRpcSystemProperties.setProperties(TRpcSystemProperties.CONFIG_PATH, pathToServerYaml);
Main.main(args);
}
}- 示例代码可参考:
trpc-java-demo/src/main/java/com/tencent/trpc/demo/example/yaml/ServerTest.java。 - 示例代码执行方式:
$ mvn exec:java -pl :trpc-java-demo -Dexec.cleanupDaemonThreads=false -Dexec.mainClass=com.tencent.trpc.demo.example.yaml.ServerTest
客户端YAML配置文件定义好后,可使用如下方式启动客户端:
class Client {
public static void main(String[] args) {
TRpcSystemProperties.setProperties(TRpcSystemProperties.CONFIG_PATH, pathToServerYaml);
Main.main(args);
GreeterserviceAPI proxy = TRpcProxy.getProxy("trpc.TestApp.TestServer.Greeter1",
GreeterserviceAPI.class);
// do some test with proxy
}
}- 示例代码可参考:
trpc-java-demo/src/main/java/com/tencent/trpc/demo/example/yaml/ClientTest.java。 - 示例代码执行方式:
$ mvn exec:java -pl :trpc-java-demo -Dexec.cleanupDaemonThreads=false -Dexec.mainClass=com.tencent.trpc.demo.example.yaml.ClientTest
上述示例均演示的是普通RPC服务,tRPC-Java除支持普通RPC请求外,还支持流式RPC、HTTP RPC等多种协议,部分说明如下文。
流式RPC支持客户端与服务端之间进行更加灵活的交互。它分为如下三种类型:
- 客户端流式:允许客户端依次发送多个包,服务端全收到后,再返回一个包。它是多对一关系。
- 服务端流式:允许服务端为一个客户端请求生成多次回包。它是一对多关系。
- 双向流式:允许客户端和服务端可以并行地给对方发送请求。它是多对多关系。
同一次请求创建的流中,接收方收到消息的顺序与请求方发送的顺序是完全一致的。
与普通RPC不同,在protobuf中声明流式RPC需要使用stream关键字,如下例中定义了三种类型的流式接口:
service StreamGreeterService {
rpc clientSayHellos (stream HelloRequest) returns (HelloResponse);
rpc serverSayHellos (HelloRequest) returns (stream HelloResponse);
rpc allSayHellos (stream HelloRequest) returns (stream HelloResponse);
}注:由于实现差异,
流式RPC与普通RPC的接口不支持定义在同一个Service中。
tRPC-Java的流式功能基于 Reactor 实现,如上述protobuf里定义的流式接口会分别对应成如下代码中的函数:
@TRpcService(name = "trpc.TestApp.TestServer.Greeter")
public interface StreamGreeterService {
@TRpcMethod(name = "serverSayHellos")
Flux<HelloRequestProtocol.HelloResponse> serverSayHellos(RpcContext ctx,
HelloRequestProtocol.HelloRequest request);
@TRpcMethod(name = "clientSayHellos")
Mono<HelloRequestProtocol.HelloResponse> clientSayHellos(RpcContext ctx,
Publisher<HelloRequestProtocol.HelloRequest> requests);
@TRpcMethod(name = "allSayHellos")
Flux<HelloRequestProtocol.HelloResponse> allSayHellos(RpcContext ctx,
Publisher<HelloRequestProtocol.HelloRequest> requests);
}由于
Flux和Mono的消息数量要求,从函数接口即可推断出是哪种类型的流式接口。
示例配置可参考:trpc-java-demo/src/main/proto/demo.proto。
流式RPC和普通RPC的配置类似,详细可参考示例代码:trpc-java-demo/src/main/java/com/tencent/trpc/demo/example/stream。
示例代码的启动方式如下:
- 启动服务端示例代码
$ mvn exec:java -pl :trpc-java-demo -Dexec.cleanupDaemonThreads=false -Dexec.mainClass=com.tencent.trpc.demo.example.stream.ServerTest
- 启动客户端示例代码
$ mvn exec:java -pl :trpc-java-demo -Dexec.cleanupDaemonThreads=false -Dexec.mainClass=com.tencent.trpc.demo.example.stream.ClientTest
在tRPC-Java中,将普通RPC服务转换为HTTP协议,仅需改动YAML配置文件对应service的如下两处:
protocol从trpc(默认值) 改为http。transporter从netty(默认值)改为jetty。
示例配置可参考:trpc-java-demo/src/main/resources/trpc_java_server.yaml。
上述示例配置中trpc.TestApp.TestServer.Greeter3
对应的服务即为HTTP服务。对应服务的默认URL地址为/{@TRpcService.name}/{@TRpcMethod.name}
(即将服务注解中的信息拼接起来),如示例trpc.TestApp.TestServer.Greeter3
对应的URL地址为http://127.0.0.1:12322/trpc.TestApp.TestServer.GreeterService/sayHello。
可使用如下命令访问对应服务:
$ curl -XPOST -H"Content-Type: application/json" -d'{"message": "tRPC-Java"}' http://127.0.0.1:12322/trpc.TestApp.TestServer.GreeterService/sayHello除了基础的HTTP访问方式外,tRPC-Java还支持在客户端YAML配置中配置远程HTTP协议的tRPC服务,示例配置可参考:trpc-java-demo/src/main/resources/trpc_java_client.yaml
。
如需要更改默认的URL地址,可以在protobuf中声明服务时,加上下面的扩展:
import "trpc.proto";
service GreeterService2 {
rpc sayHi (HelloRequest) returns (HelloResponse) {
option (trpc.alias) = "/api/hi";
}
}示例配置可参考:trpc-java-demo/src/main/proto/demo.proto。
修改后的URL访问方式如:
$ curl -XPOST -H"Content-Type: application/json" -d'{"message": "tRPC-Java"}' http://127.0.0.1:12322/api/hitRPC-Java与Spring Boot等Spring生态有良好的支持。在Spring Boot中,tRPC-JAVA原本的YAML配置文件可以改成Spring
Boot中的application.yml来配置,示例配置可参考:trpc-spring-server-demo/src/main/resources/application.yml。
配置文件配置好后,仅需在Spring Boot的启动类中,添加@EnableTRpc注解即可,无需再像trpc-java-demo中手动的方式启动tRPC-Java。
@EnableTRpc
@SpringBootApplication
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
}示例代码可参考:trpc-spring-server-demo/src/main/com/tencent/trpc/spring/demo/server/ServerApplication.java
。
如果希望tRPC-Java与SpringMvc集成,则需在pom.xml中引入如下依赖:
<dependency>
<groupId>com.tencent.trpc</groupId>
<artifactId>trpc-springmvc</artifactId>
<version>${tRPC-Java.version}</version>
</dependency>之后再继续编写正常的SpringMvc Controller或tRPC-Java服务即可。
示例代码参考:trpc-spring-server-demo/src/main/com/tencent/trpc/spring/demo/server/controller/。
由于示例代码未配置Spring Boot插件,可按如下的方式启动对应示例代码:
- 启动服务端示例代码
$ mvn exec:java -pl :trpc-spring-server-demo -Dexec.cleanupDaemonThreads=false -Dexec.mainClass=com.tencent.trpc.spring.demo.ServerApplication
- 启动客户端示例代码
$ mvn exec:java -pl :trpc-spring-client-demo -Dexec.cleanupDaemonThreads=false -Dexec.mainClass=com.tencent.trpc.spring.demo.ClientApplication
正常的Spring Boot程序直接打成单独的Jar包执行即可。
tRPC-Java有着丰富的可扩展性,拦截器可以在请求执行的流程中注入各种新能力,插件则能更方便地集成新功能。
拦截器
像一棵洋葱,当tRPC-Java处理请求时,会依次经过洋葱的每一层。
如下例所示,实现一个拦截器,需继承并实现Filter接口:
public class DemoFilter implements Filter {
@Override
public CompletionStage<Response> filter(Invoker<?> filterChain, Request req) {
try {
System.out.println("enter filter");
return filterChain.invoke(req)
.whenComplete((r, t) -> System.out.println("filter completed"));
} finally {
System.out.println("exit filter");
}
}
}示例代码参考:trpc-java-demo/src/main/com/tencent/trpc/demo/filter/DemoFilter.java。
拦截器代码写完后,需通过META-INF/trpc/com.tencent.trpc.core.filter.spi.Filter文件来命名并注册拦截器:
demoFilter=com.tencent.trpc.demo.filter.DemoFilter拦截器注册好之后,即可在YAML配置文件中声明使用:
client:
filters: # 指定客户端全局拦截器
- filter1
- filter2
service:
- name: xxx
filters: # 指定单个客户端特有的拦截器,它们会追加在全局拦截器之后
- filter3
server:
filters: # 指定服务端全局拦截器
- filter1
- filter2
service:
- name: yyy
filters: # 指定单个服务端特有的拦截器,它们会追加在全局拦截器之后
- filter3示例配置参考:trpc-java-demo/src/main/resources/trpc_java_server.yaml。
tRPC-Java支持类似Java
SPI的插件扩展机制,具体代码可参考 插件
。
需要支持插件能力的接口,可通过@Extensible注解标注,如:
@Extensible
public interface WorkerPool {
// ...
}当实现插件时,可通过@Extension注解标注,如:
@Extension("thread")
public class ThreadWorkerPool implements WorkerPool {
// ...
}编写好的插件,需要采用类似SPI的机制,在
META-INF/trpc/目录下编写对应插件的查找文件,具体可参考前文拦截器处的示例。
当需要使用插件时,可通过ExtensionLoader类获取即可:
WorkerPool workerPool=ExtensionLoader.getExtensionLoader(WorkerPool.class).getExtension("thread");
// do with workerPool- 阅读 桩代码生成工具 来更深入地了解
tRPC-Java。