本文为笔者阅读鱼皮的项目 《简易版 RPC 框架开发》的笔记,如果有时间可以直接去看原文,
1. 简易版 RPC 框架开发
前面的内容可以笔者的前面几篇笔记
鱼皮项目简易版 RPC 框架开发(一)
鱼皮项目简易版 RPC 框架开发(二)
鱼皮项目简易版 RPC 框架开发(三)
引用:
1. 简易版 RPC 框架开发
鱼皮项目简易版 RPC 框架开发(一)
鱼皮项目简易版 RPC 框架开发(二)
鱼皮项目简易版 RPC 框架开发(三)
RPC框架的简单理解
HTTP 请求处理源代码
package com.yupi.yurpc.server;import com.yupi.yurpc.model.RpcRequest;
import com.yupi.yurpc.model.RpcResponse;
import com.yupi.yurpc.registry.LocalRegistry;
import com.yupi.yurpc.serializer.JdkSerializer;
import com.yupi.yurpc.serializer.Serializer;
import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;import java.io.IOException;
import java.lang.reflect.Method;/*** HTTP 请求处理*/
public class HttpServerHandler implements Handler<HttpServerRequest> {@Overridepublic void handle(HttpServerRequest request) {// 指定序列化器final Serializer serializer = new JdkSerializer();// 记录日志System.out.println("Received request: " + request.method() + " " + request.uri());// 异步处理 HTTP 请求request.bodyHandler(body -> {byte[] bytes = body.getBytes();RpcRequest rpcRequest = null;try {rpcRequest = serializer.deserialize(bytes, RpcRequest.class);} catch (Exception e) {e.printStackTrace();}// 构造响应结果对象RpcResponse rpcResponse = new RpcResponse();// 如果请求为 null,直接返回if (rpcRequest == null) {rpcResponse.setMessage("rpcRequest is null");doResponse(request, rpcResponse, serializer);return;}try {// 获取要调用的服务实现类,通过反射调用Class<?> implClass = LocalRegistry.get(rpcRequest.getServiceName());if (implClass == null) {rpcResponse.setMessage("Service not found: " + rpcRequest.getServiceName());doResponse(request, rpcResponse, serializer);return;}Method method = implClass.getMethod(rpcRequest.getMethodName(), rpcRequest.getParameterTypes());Object result = method.invoke(implClass.newInstance(), rpcRequest.getArgs());// 封装返回结果rpcResponse.setData(result);rpcResponse.setDataType(method.getReturnType());rpcResponse.setMessage("ok");} catch (Exception e) {e.printStackTrace();rpcResponse.setMessage(e.getMessage());rpcResponse.setException(e);}// 响应doResponse(request, rpcResponse, serializer);});}/*** 响应* @param request* @param rpcResponse* @param serializer*/void doResponse(HttpServerRequest request, RpcResponse rpcResponse, Serializer serializer) {HttpServerResponse httpServerResponse = request.response().putHeader("content-type", "application/json");try {// 序列化byte[] serialized = serializer.serialize(rpcResponse);httpServerResponse.end(Buffer.buffer(serialized));} catch (IOException e) {e.printStackTrace();httpServerResponse.end(Buffer.buffer());}}
}
代码功能概述
这段代码实现了一个基于 HTTP 协议的 RPC 服务器请求处理器,负责接收客户端请求、反射调用本地服务方法并返回响应结果。核心功能包括请求反序列化、服务方法调用、结果封装和序列化响应。
核心组件分析
序列化器
- 使用
JdkSerializer
进行请求和响应的序列化与反序列化。 - 在
doResponse
方法中将响应对象序列化为字节流。
请求处理流程
- 通过
request.bodyHandler
异步处理请求体。 - 将请求体字节流反序列化为
RpcRequest
对象。 - 若反序列化失败,返回包含错误信息的响应。
服务调用机制
- 通过
LocalRegistry.get()
根据服务名获取实现类。 - 使用反射机制调用目标方法:
implClass.getMethod()
获取方法,method.invoke()
执行调用。 - 捕获调用过程中的异常并封装到响应中。
响应构建
- 成功调用时设置方法返回值到
rpcResponse.data
。 - 异常时设置异常信息到
rpcResponse.message
和rpcResponse.exception
。
关键方法说明
handle()
- 主处理方法,接收
HttpServerRequest
对象。 - 采用异步非阻塞方式处理请求体。
- 协调反序列化、服务调用和响应流程。
doResponse()
- 设置 HTTP 响应头
content-type: application/json
。 - 将
RpcResponse
序列化为字节流写入响应体。 - 异常时返回空缓冲区。
补充
JdkSerializer
JDK序列化是Java平台提供的一种对象序列化机制,通过java.io.Serializable
接口实现。它允许将对象转换为字节流,便于存储或传输,并能在需要时重新构造为原始对象。
详细分析见:
鱼皮项目简易版 RPC 框架开发(三)
doResponse
序列化响应对象为字节流的方法
在 doResponse
方法中,将响应对象序列化为字节流通常涉及以下几个关键步骤:
使用 JSON 序列化库
常见的 JSON 序列化库如 Jackson
、Gson
或 Fastjson
可以将 Java 对象转换为 JSON 字符串,再进一步转为字节流。例如,使用 Jackson
的 ObjectMapper
:
ObjectMapper objectMapper = new ObjectMapper();
byte[] responseBytes = objectMapper.writeValueAsBytes(responseObject);
手动构建字节流
如果需要自定义格式,可以直接拼接字符串并调用 getBytes()
方法转换为字节流。例如:
String responseString = "{\"status\":\"success\",\"data\":" + customData + "}";
byte[] responseBytes = responseString.getBytes(StandardCharsets.UTF_8);
使用协议缓冲区(Protocol Buffers)
对于高性能场景,可以通过 Protocol Buffers 定义消息格式并生成字节流:
ResponseProto.Response response = ResponseProto.Response.newBuilder().setStatus("OK").setData(data).build();
byte[] responseBytes = response.toByteArray();
设置正确的 Content-Type 和编码
确保响应头中包含正确的 Content-Type
和字符编码(如 application/json; charset=UTF-8
),以便客户端正确解析字节流。
处理异常情况
捕获序列化过程中可能抛出的异常(如 JsonProcessingException
),并返回错误信息或默认响应。例如:
try {byte[] responseBytes = objectMapper.writeValueAsBytes(responseObject);// 发送字节流到输出流
} catch (JsonProcessingException e) {byte[] errorBytes = "{\"error\":\"Serialization failed\"}".getBytes();
}
优化性能
对于高频调用场景,可以复用序列化工具实例(如 ObjectMapper
),避免重复创建对象带来的开销。
printStackTrace
理解 printStackTrace 的作用
printStackTrace()
是 Java 中 Throwable
类的方法,用于将异常的堆栈跟踪信息输出到标准错误流(System.err)。它显示了异常的类型、消息以及从方法调用栈顶到底的完整路径,帮助开发者快速定位问题根源。
使用场景
- 调试阶段:在开发或测试阶段,通过打印堆栈跟踪快速定位异常发生的代码位置。
- 日志记录:结合日志框架(如 Log4j、SLF4J),将堆栈信息写入日志文件而非直接打印到控制台。
代码示例
try {// 可能抛出异常的代码int result = 10 / 0;
} catch (ArithmeticException e) {e.printStackTrace(); // 打印堆栈跟踪到 System.err
}
输出格式说明
堆栈跟踪通常包含以下内容:
- 异常类型:如
ArithmeticException
。 - 异常消息:如
/ by zero
。 - 调用栈:从触发异常的方法到最外层的调用方法,每行显示类名、方法名、文件名和行号。
示例输出:
java.lang.ArithmeticException: / by zeroat com.example.Test.main(Test.java:10)
替代方案(生产环境推荐)
直接使用 printStackTrace()
在生产环境中可能不够灵活,建议:
- 日志框架:通过
logger.error("Error occurred", e)
记录异常,支持分级存储和格式化。 - 自定义输出:重定向堆栈跟踪到字符串或文件,例如:
StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); String stackTrace = sw.toString();
注意事项
- 性能影响:频繁调用
printStackTrace()
可能影响性能,尤其在循环或高频操作中。 - 信息暴露:堆栈跟踪可能暴露敏感信息(如内部类名),需谨慎处理。
通过合理使用 printStackTrace()
或其替代方案,可以高效排查异常问题。