本文為筆者閱讀魚皮的項目 《簡易版 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()
或其替代方案,可以高效排查異常問題。