写在前面
简介
通过封装将grpc的技术使用难度降低到普通Java程序的开发。
cn.com.yd.commons.grpc提供了4种形式的服务接口定义以适应不同的应用场景。
cn.com.yd.commons.grpc将请求参数和响应参数定义为bytes以统一因业务不同而导致的差异性;使用cglib进行动态代理,在grpc的方法中执行具体的业务处理。
在具体应用中应将此工程使用Maven打包后作为依赖引入目标工程中。
环境
JDK:1.8
GRPC:1.6.1
Protobuf:3.3.0
cglib:3.2.5
IDE:开发工具IDEA
Maven工程结构图
特色
- 本地无需搭建GRPC环境,只需导入依赖即可
- 开发平台不限于STS、IDEA
- GRPC功能通用化
- 使用GRPC像开发普通程序一样
- 只需简单的少量配置
详细说明
开发环境准备
IDEA自带插件支持grpc开发,所以开发工具选择IDEA。在正式开始之前需要先安装Protobuf Support插件。
依次点击Intellij中的“File”-->"Settings"-->"Plugins"-->"Browse repositories",如图
输入Protobuf,如下所示
安装完后,重启IDEA,插件安装完毕。
proto定义
在main目录下新建一个名为proto的文件夹,请确保文件夹的所处位置以及名称都正确,否则将不能进行编译,而且不报任何错误。
//指定正在使用proto3语法,如果你没有指定这个,编译器会使用proto2。这个指定语法行必须是文件的非空非注释的第一个行。
syntax = "proto3";
option java_package = "cn.com.yd.commons.grpc";
option java_outer_classname = "GrpcService";
option java_multiple_files = true;
// 定义通用的 Grpc 服务
service CommonService {
// 处理请求
rpc handle (Request) returns (Response) {
}
// 处理请求,服务端流式
rpc serverStreamingHandle (Request) returns (stream Response) {
}
// 处理请求,客户端流式
rpc clientStreamingHandle (stream Request) returns (Response) {
}
// 处理请求,双向流式
rpc bidirectionalStreamingHandle (stream Request) returns (stream Response) {
}
}
// 定义通用的 Grpc 请求体
message Request {
bytes request = 1;
}
// 定义通用的 Grpc 响应体
message Response {
bytes reponse = 1;
}
pom.xml中的配置
4.0.0
cn.com.yd.commons
grpc
1.0.0
jar
grpc
grpc 基础类
UTF-8
UTF-8
1.8
1.11.0
3.3.0
io.grpc
grpc-netty
${grpc.version}
io.grpc
grpc-protobuf
${grpc.version}
io.grpc
grpc-stub
${grpc.version}
io.protostuff
protostuff-core
1.6.0
io.protostuff
protostuff-runtime
1.6.0
com.alibaba
fastjson
1.2.40
cglib
cglib
3.2.5
kr.motd.maven
os-maven-plugin
1.5.0.Final
org.xolstice.maven.plugins
protobuf-maven-plugin
0.5.1
com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
grpc-java
io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
compile
compile-custom
org.apache.maven.plugins
maven-compiler-plugin
1.8
编译
proto文件编写完成后进行编译以生成对应的class文件,编译后的效果大致如图
辅助工具类
GrpcHelper
其中包含了Request、Response与JSONObject之间的转换等,主要是对应用的辅助。
package cn.com.yd.commons.grpc.util;
import cn.com.yd.commons.grpc.Request;
import cn.com.yd.commons.grpc.Response;
import com.alibaba.fastjson.JSONObject;
import com.google.protobuf.ByteString;
import io.protostuff.LinkedBuffer;
import io.protostuff.ProtostuffIOUtil;
import io.protostuff.runtime.RuntimeSchema;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.reflect.FastClass;
import net.sf.cglib.reflect.FastMethod;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class GrpcHelper {
/**
* 转换成GRPC方法需要的参数
*
* @param bean Spring管理的bean实例名称
* @param method 要执行的业务方法
* @param args 业务方法的参数,可以为null
* @return
*/
public static Request toRequest(String bean, String method, Object[] args) {
JSONObject json = new JSONObject();
json.put("bean", bean);
json.put("method", method);
if (null != args) {
json.put("args", args);
}
byte[] bytes = ProtobufUtils.serialize(json);
return Request.newBuilder().setRequest(ByteString.copyFrom(bytes)).build();
}
/**
* 将GRPC方法的请求参数转换成JSONObject
*
* @param request GRPC方法的请求参数
* @return
*/
public static JSONObject toJSONObject(Request request) {
return ProtobufUtils.deserialize(request.getRequest().toByteArray(), JSONObject.class);
}
/**
* 将响应结果转换成JSONObject
* @param response 响应结果
* @return
*/
public static JSONObject toJSONObject(Response response) {
return ProtobufUtils.deserialize(response.getReponse().toByteArray(), JSONObject.class);
}
public static JSONObject success() {
JSONObject json = new JSONObject();
json.put("state", true);
json.put("msg", "成功");
json.put("obj", null);
return json;
}
/**
* 执行具体的业务方法并返回执行结果
*
* @param bean Spring管理的bean实例
* @param method 要执行的业务方法
* @param args 业务方法的参数,可以为null
* @return
*/
public static Object execute(Object bean, String method, Object[] args) {
Object result = null;
try {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(bean.getClass());
FastClass serviceFastClass = FastClass.create(bean.getClass());
Class>[] argTypes = null;
if (args != null) {
argTypes = new Class[args.length];
for (int i = 0; i < args.length; i++) {
Class> type = args[i].getClass();
argTypes[i] = type;
}
}
FastMethod serviceFastMethod = serviceFastClass.getMethod(method, argTypes);
result = serviceFastMethod.invoke(bean, args);
} catch (Exception e) {
result = e;
e.printStackTrace();
}
return result;
}
/**
* 将执行结果转换成Response
* @param result 执行结果
* @return
*/
public static Response toResponse(Object result) {
JSONObject json = null;
if (result instanceof JSONObject) {
json = (JSONObject) result;
} else {
json = new JSONObject();
if (result instanceof Throwable) {
json.put("state", false);
json.put("msg", "失败");
json.put("obj", ((Throwable) result).getMessage());
} else {
json.put("state", true);
json.put("msg", "成功");
json.put("obj", result);
}
}
return Response.newBuilder().setReponse(ByteString.copyFrom(ProtobufUtils.serialize(json))).build();
}
}
class ProtobufUtils {
// 缓存 schema 对象的 map
private static Map, RuntimeSchema>> cachedSchema = new ConcurrentHashMap<>();
/**
* 根据获取相应类型的schema方法
*/
@SuppressWarnings({"unchecked", "unused"})
private static RuntimeSchema getSchema(Class clazz) {
RuntimeSchema schema = (RuntimeSchema) cachedSchema.get(clazz);
if (schema == null) {
schema = RuntimeSchema.createFrom(clazz);
cachedSchema.put(clazz, schema);
}
return schema;
}
/**
* 序列化方法,将对象序列化为字节数组(对象 ---> 字节数组)
*/
@SuppressWarnings("unchecked")
public static byte[] serialize(T obj) {
Class clazz = (Class) obj.getClass();
RuntimeSchema schema = getSchema(clazz);
LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
return ProtostuffIOUtil.toByteArray(obj, schema, buffer);
}
/**
* 反序列化方法,将字节数组反序列化为对象(字节数组 ---> 对象)
*/
public static T deserialize(byte[] data, Class clazz) {
RuntimeSchema schema = RuntimeSchema.createFrom(clazz);
T message = schema.newMessage();
ProtostuffIOUtil.mergeFrom(data, message, schema);
return message;
}
}
GrpcServer
package cn.com.yd.commons.grpc.util;
import io.grpc.BindableService;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import java.io.IOException;
public class GrpcServer {
private final Server server;//服务器
public GrpcServer(int port, BindableService... services) throws IOException {
//初始化Server参数
ServerBuilder builder = ServerBuilder.forPort(port);
for(BindableService bs:services){
builder.addService(bs);
}
server = builder.build();
}
/**
* 启动服务
*/
public void start() throws IOException {
server.start();
System.out.println("Server started, listening on " + server.getPort());
//程序退出时关闭资源
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.err.println("*** shutting down gRPC server since JVM is shutting down");
GrpcServer.this.stop();
System.err.println("*** server shut down");
}));
}
/**
* 关闭服务
*/
public void stop() {
if (server != null) {
server.shutdown();
}
}
/**
* 使得server一直处于运行状态
*/
public void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
}
GrpcClient
import cn.com.yd.commons.grpc.CommonServiceGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import java.util.concurrent.TimeUnit;
public class GrpcClient {
private static ManagedChannel channel;//grpc信道,需要指定端口和地址
private static CommonServiceGrpc.CommonServiceBlockingStub blockingStub;//阻塞/同步存根
private static CommonServiceGrpc.CommonServiceStub asyncStub;//非阻塞,异步存根
public GrpcClient(String server, int port) {
System.out.println("server:"+server+",port:"+port);
//创建信道
channel = ManagedChannelBuilder.forAddress(server, port)
.usePlaintext()
.build();
//创建存根
blockingStub = CommonServiceGrpc.newBlockingStub(channel);
asyncStub = CommonServiceGrpc.newStub(channel);
}
/**
* 关闭方法
*/
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
public CommonServiceGrpc.CommonServiceBlockingStub getBlockingStub() {
return blockingStub;
}
public CommonServiceGrpc.CommonServiceStub getAsyncStub() {
return asyncStub;
}
}
工程源码
暂不公开。