鱼皮项目简易版 RPC 框架开发(四)

本文为笔者阅读鱼皮的项目 《简易版 RPC 框架开发》的笔记,如果有时间可以直接去看原文,

1. 简易版 RPC 框架开发

前面的内容可以笔者的前面几篇笔记

鱼皮项目简易版 RPC 框架开发(一)

鱼皮项目简易版 RPC 框架开发(二)

鱼皮项目简易版 RPC 框架开发(三)

引用:

1. 简易版 RPC 框架开发

鱼皮项目简易版 RPC 框架开发(一)

鱼皮项目简易版 RPC 框架开发(二)

鱼皮项目简易版 RPC 框架开发(三)

RPC框架的简单理解

HTTP 请求处理源代码

java 复制代码
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> {

    @Override
    public 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.messagerpcResponse.exception

关键方法说明

handle()

  • 主处理方法,接收 HttpServerRequest 对象。
  • 采用异步非阻塞方式处理请求体。
  • 协调反序列化、服务调用和响应流程。

doResponse()

  • 设置 HTTP 响应头 content-type: application/json
  • RpcResponse 序列化为字节流写入响应体。
  • 异常时返回空缓冲区。

补充

JdkSerializer

JDK序列化是Java平台提供的一种对象序列化机制,通过java.io.Serializable接口实现。它允许将对象转换为字节流,便于存储或传输,并能在需要时重新构造为原始对象。

详细分析见:

鱼皮项目简易版 RPC 框架开发(三)

doResponse

序列化响应对象为字节流的方法

doResponse 方法中,将响应对象序列化为字节流通常涉及以下几个关键步骤:

使用 JSON 序列化库

常见的 JSON 序列化库如 JacksonGsonFastjson 可以将 Java 对象转换为 JSON 字符串,再进一步转为字节流。例如,使用 JacksonObjectMapper

复制代码
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
}

输出格式说明

堆栈跟踪通常包含以下内容:

  1. 异常类型 :如 ArithmeticException
  2. 异常消息 :如 / by zero
  3. 调用栈:从触发异常的方法到最外层的调用方法,每行显示类名、方法名、文件名和行号。

示例输出:

复制代码
java.lang.ArithmeticException: / by zero
    at 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() 或其替代方案,可以高效排查异常问题。

相关推荐
aodunsoft9 分钟前
安全月报 | 傲盾DDoS攻击防御2025年7月简报
网络·安全·ddos
zzc92125 分钟前
USRP捕获手机/路由器数据传输信号波形(下)
网络·5g·路由器·ofdm·mimo·tenda·双工
hack:D_K1 小时前
网络基础——路由控制
网络·经验分享·笔记·路由
DemonAvenger1 小时前
Go语言中的gRPC:原理与实战
网络协议·架构·go
mortimer1 小时前
Tenacity:一行代码实现简洁优雅的遇错自动重试逻辑
人工智能·python·网络协议
21号 12 小时前
10. NAT,代理服务,内网穿透
linux·服务器·网络
敲上瘾2 小时前
从“碎片化”到“完美重组”:IP报文的分片艺术
网络·网络协议·tcp/ip·ip
_Rookie._9 小时前
http触发预检请求条件
网络·网络协议·http
NewCarRen10 小时前
汽车电子控制系统开发的整体安全理念
网络·安全·汽车
fatiaozhang952712 小时前
中兴云电脑W101D2-晶晨S905L3A-2G+8G-安卓9-线刷固件包
android·网络·电脑·电视盒子·刷机固件·机顶盒刷机