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

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

1. 简易版 RPC 框架开发

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

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

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

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

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

引用:

1. 简易版 RPC 框架开发

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

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

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

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

RPC框架的简单理解

详解JDK动态代理类Proxy.newProxyInstance()

服务代理(JDK 动态代理)

ServiceProxy类(服务代理(JDK 动态代理))

java 复制代码
package com.yupi.yurpc.proxy;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.yupi.yurpc.model.RpcRequest;
import com.yupi.yurpc.model.RpcResponse;
import com.yupi.yurpc.serializer.JdkSerializer;
import com.yupi.yurpc.serializer.Serializer;

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 服务代理(JDK 动态代理)
 */
public class ServiceProxy implements InvocationHandler {

    /**
     * 调用代理
     *
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 指定序列化器
        Serializer serializer = new JdkSerializer();

        // 构造请求
        RpcRequest rpcRequest = RpcRequest.builder()
                .serviceName(method.getDeclaringClass().getName())
                .methodName(method.getName())
                .parameterTypes(method.getParameterTypes())
                .args(args)
                .build();
        try {
            // 序列化
            byte[] bodyBytes = serializer.serialize(rpcRequest);
            // 发送请求
            // todo 注意,这里地址被硬编码了(需要使用注册中心和服务发现机制解决)
            String serverUrl = System.getProperty("rpc.server.url", "http://localhost:8080");
            try (HttpResponse httpResponse = HttpRequest.post(serverUrl)
                    .body(bodyBytes)
                    .execute()) {
                byte[] result = httpResponse.bodyBytes();
                // 反序列化
                RpcResponse rpcResponse = serializer.deserialize(result, RpcResponse.class);
                if (rpcResponse.getException() != null) {
                    throw new RuntimeException("RPC调用异常: " + rpcResponse.getMessage(), rpcResponse.getException());
                }
                return rpcResponse.getData();
            }
        } catch (IOException e) {
            throw new RuntimeException("网络请求异常", e);
        }
    }
}

代码功能概述

该代码实现了一个基于JDK动态代理的RPC客户端代理类,用于远程调用服务。核心功能包括序列化请求、发送HTTP请求、处理响应及异常。

核心类与接口

  • ServiceProxy:实现InvocationHandler接口,作为动态代理的调用处理器。
  • RpcRequest:封装RPC请求信息(服务名、方法名、参数等)。
  • RpcResponse:封装RPC响应数据及异常信息。
  • Serializer:序列化接口,此处使用JdkSerializer实现。

关键逻辑流程

代理调用入口 invoke方法在代理对象调用方法时触发:

复制代码
public Object invoke(Object proxy, Method method, Object[] args)

请求构造 通过反射获取方法信息,构建RpcRequest对象:

复制代码
RpcRequest rpcRequest = RpcRequest.builder()
    .serviceName(method.getDeclaringClass().getName())
    .methodName(method.getName())
    .parameterTypes(method.getParameterTypes())
    .args(args)
    .build();

序列化与网络请求

  1. 使用JDK序列化请求对象:

    byte[] bodyBytes = serializer.serialize(rpcRequest);

  2. 发送HTTP POST请求(硬编码地址,需改进):

    String serverUrl = System.getProperty("rpc.server.url", "http://localhost:8080");
    HttpResponse httpResponse = HttpRequest.post(serverUrl)
    .body(bodyBytes)
    .execute();

响应处理

  1. 反序列化响应字节流为RpcResponse对象:

    RpcResponse rpcResponse = serializer.deserialize(result, RpcResponse.class);

  2. 异常检查与结果返回:

    if (rpcResponse.getException() != null) {
    throw new RuntimeException("RPC调用异常", rpcResponse.getException());
    }
    return rpcResponse.getData();

待优化点

  1. 硬编码问题:服务地址通过系统属性或默认值获取,建议改用服务发现机制。
  2. 异常处理:当前仅包装异常,可细化网络超时、序列化失败等场景。
  3. 性能优化:考虑连接池复用、异步请求等机制。

扩展建议

  • 替换为JSON或二进制序列化(如Hessian、Protobuf)。
  • 增加重试机制和负载均衡策略。
  • 支持多种协议(如gRPC、WebSocket)。

ServiceProxyFactory类(服务代理工厂(用于创建代理对象))

java 复制代码
package com.yupi.yurpc.proxy;

import java.lang.reflect.Proxy;

/**
 * 服务代理工厂(用于创建代理对象)
 */
public class ServiceProxyFactory {

    /**
     * 根据服务类获取代理对象
     *
     * @param serviceClass
     * @param <T>
     * @return
     */
    public static <T> T getProxy(Class<T> serviceClass) {
        return (T) Proxy.newProxyInstance(
                serviceClass.getClassLoader(),
                new Class[]{serviceClass},
                new ServiceProxy());
    }
}

代码功能解析

这段代码定义了一个名为ServiceProxyFactory的工厂类,用于动态生成指定接口的代理对象。核心功能是通过Java的动态代理机制创建服务接口的代理实例。

关键组件分析

Proxy.newProxyInstance方法参数说明

  • 参数1:serviceClass.getClassLoader() 使用目标接口的类加载器加载代理类
  • 参数2:new Class[]{serviceClass} 代理类需要实现的接口数组(这里只代理单个接口)
  • 参数3:new ServiceProxy() 自定义的InvocationHandler实现,处理代理方法调用

动态代理实现机制

当调用代理对象的方法时,会路由到ServiceProxyinvoke方法(未展示但需实现)。典型的RPC框架中,此处理器会完成:

  1. 方法调用信息序列化
  2. 网络请求发送
  3. 响应结果反序列化
  4. 返回调用结果

泛型设计特点

方法签名public static <T> T getProxy(Class<T> serviceClass)表明:

  • 支持任意接口类型T
  • 返回类型与输入类型严格匹配
  • 编译时类型安全检查

典型使用场景

复制代码
// 声明服务接口
public interface UserService {
    User getUserById(long id);
}

// 获取代理实例
UserService proxy = ServiceProxyFactory.getProxy(UserService.class);
// 调用代理方法(实际触发ServiceProxy处理)
User user = proxy.getUserById(123);

实现注意事项

  1. ServiceProxy需实现InvocationHandler接口

  2. 建议添加参数校验:

    复制代码
    if (!serviceClass.isInterface()) {
        throw new IllegalArgumentException("serviceClass must be interface");
    }
  3. 考虑添加缓存机制避免重复创建代理类

性能优化方向

  1. 代理类缓存(ClassLoader级别)
  2. 方法调用结果缓存
  3. 异步调用支持

该模式常见于RPC框架客户端实现,将本地接口调用转换为远程过程调用。

补充

newProxyInstance()

Proxy.newProxyInstance() 是 Java JDK 提供的一个方法,用于创建动态代理对象。

动态代理是一种在运行时创建代理对象的机制,允许我们在不直接访问原始对象的情况下,通过代理对象来调用原始对象的方法。

newProxyInstance() 方法的签名如下:

java 复制代码
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
    throws IllegalArgumentException

该方法接受三个参数:

ClassLoader loader:表示类加载器,用于加载生成的代理类。可以通过目标类的 ClassLoader 实例或者其他合适的类加载器来指定。

Class<?>[] interfaces:表示代理类要实现的接口列表。这是一个数组,指定了代理类要实现的接口,通过代理对象调用接口中的方法时,实际上会被转发给 InvocationHandler 的

invoke() 方法处理。

InvocationHandler h:表示代理对象的调用处理程序,也是一个实现了 InvocationHandler 接口的对象。InvocationHandler 接口中只有一个方法 invoke(),当代理对象的方法被调用时,会被传递到 invoke() 方法中进行处理。

复制代码
Proxy.newProxyInstance( 
        serviceClass.getClassLoader(),         // ① 类加载器 
        new Class[]{serviceClass},             // ② 接口数组 
        new ServiceProxy()                     // ③ 调用处理器 
);

源代码中的参数

具体使用在下一篇用户测试类讲解()
动态代理的作用是允许我们在调用方法之前或之后执行一些额外的操作,比如记录日志、实现事务管理等。通过 newProxyInstance() 方法创建的代理对象会自动关联到指定的 InvocationHandler,并将方法调用委托给 InvocationHandler 来处理。

总之,Proxy.newProxyInstance() 方法用于创建动态代理对象,并将方法调用转发给指定的 InvocationHandler 处理。

invoke()

在 InvocationHandler 接口中的 invoke() 方法中,有三个参数:

Object proxy:代理对象本身。在 invoke() 方法中,可以使用 proxy 参数来调用代理对象的其他方法,或者在特定情况下使用代理对象本身。

Method method:被调用的方法对象。method 参数表示当前正在调用的方法,通过它可以获取方法的名称、参数等信息。

Object[] args:方法的参数数组。args 参数是一个对象数组,表示传递给方法的参数。通过这个数组,可以获取方法调用时传递的具体参数值。

总结起来,invoke() 方法中的三个参数的含义如下:

proxy 参数:代理对象本身,可以使用它调用代理对象的其他方法。

method 参数:被调用的方法对象 ,可以通过它获取方法的相关信息。

args 参数:方法的参数数组,可以获取方法调用时传递的具体参数值。

通过在 invoke()

方法中使用这些参数,我们可以在代理对象的方法被调用时,实现自定义的逻辑和行为,例如在方法调用前后添加日志、执行额外的操作、拦截方法调用等。
RpcRequest {

String serviceName; // 远程服务接口全限定名

String methodName; // 目标方法名称

Class<?>[] parameterTypes; // 方法参数类型数组

Object[] args; // 实际参数值数组

}

代码变量分析

ClassLoader

ClassLoader 是 Java 虚拟机(JVM)的重要组成部分,负责动态加载 Java 类到内存中。它的核心功能是将字节码文件(.class)加载到 JVM 中,并生成对应的 Class 对象。ClassLoader 采用双亲委派模型(Parent Delegation Model),确保类的唯一性和安全性。

类型

Java 中的 ClassLoader 主要分为以下三类:

  1. Bootstrap ClassLoader :由 JVM 自身实现,负责加载 Java 核心类库(如 java.lang 包)。
  2. Extension ClassLoader :加载 JAVA_HOME/lib/ext 目录下的扩展类库。
  3. Application ClassLoader:加载用户类路径(Classpath)上的类。

双亲委派模型

双亲委派模型的工作流程如下:

  • 当一个类加载请求发生时,ClassLoader 会先将请求委派给父加载器。
  • 如果父加载器无法加载,子加载器才会尝试加载。
  • 这种机制避免了类的重复加载,并确保核心类库的安全性。

ClassLoader 的应用场景

  1. 动态加载:在运行时加载类,例如插件化开发。
  2. 热部署:在不重启 JVM 的情况下更新类。
  3. 隔离加载:不同 ClassLoader 加载的类可以互相隔离,例如 Tomcat 为每个 Web 应用分配独立的 ClassLoader。

Class[]{serviceClass}

Class[]{serviceClass} 是 Java 中的一种语法,表示一个 Class 类型的数组。其中 serviceClass 是一个变量或参数,代表某个具体的类(Class)对象。这种语法通常用于反射或依赖注入等场景。

常见应用场景

反射机制

在 Java 反射中,Class[] 数组常用于表示方法参数类型或构造函数参数类型。例如:

复制代码
`Method method = clazz.getMethod("methodName", Class[]{serviceClass});`

依赖注入框架

某些依赖注入框架(如 Spring 或 Guice)可能使用 Class[] 数组来指定需要注入的依赖类型。例如:

复制代码
`@Inject
public MyService(Class<?>[] dependencies) {
    // 初始化逻辑
}`

语法说明

  • Class[]Class<?>[] 的简写形式,表示一个 Class 对象的数组。
  • serviceClass 必须是一个具体的 Class 对象,例如 String.classMyService.class
  • 如果 serviceClassnull,可能会导致 NullPointerException

ServiceProxy()

ServiceProxy 是一种设计模式或工具类,用于在分布式系统或微服务架构中简化远程服务的调用。它通常封装了网络通信细节,如序列化、反序列化、负载均衡和故障恢复,使开发者能够像调用本地方法一样调用远程服务。

ServiceProxy 的主要功能

透明远程调用 通过动态代理或接口绑定,ServiceProxy 将远程服务的方法调用转换为网络请求,隐藏底层通信细节。开发者只需定义接口,无需关心 HTTP/RPC 协议的具体实现。

负载均衡与容错 集成服务发现机制(如 Consul、Eureka),自动从注册中心获取可用服务节点列表,并通过轮询、随机等策略分发请求。支持熔断机制(如 Hystrix)防止级联故障。

协议与序列化支持 适配多种协议(REST、gRPC、Dubbo 等),支持 JSON、Protobuf 等序列化方式。例如,gRPC 的 ServiceProxy 会自动处理 Protocol Buffers 的编解码。

相关推荐
蝸牛ちゃん4 分钟前
万字深度详解DHCP服务:动态IP地址分配的自动化引擎
网络·网络协议·tcp/ip·系统架构·自动化·软考高级·dhcp
一只小bit6 小时前
Linux网络:阿里云轻量级应用服务器配置防火墙模板开放端口
linux·网络·阿里云
帽儿山的枪手7 小时前
HVV期间,如何使用SSH隧道绕过内外网隔离限制?
linux·网络协议·安全
charlie1145141918 小时前
设计自己的小传输协议 导论与概念
c++·笔记·qt·网络协议·设计·通信协议
BachelorSC8 小时前
【网络工程师软考版】网络安全
网络·安全·web安全
(Charon)9 小时前
【C语言网络编程】HTTP 客户端请求(基于 Socket 的完整实现)
网络·网络协议·http
Bryce李小白10 小时前
Kotlin实现Retrofit风格的网络请求封装
网络·kotlin·retrofit
Lovyk11 小时前
Linux网络管理
服务器·网络·php
MC皮蛋侠客12 小时前
AsyncIOScheduler 使用指南:高效异步任务调度解决方案
网络·python·fastapi
DAWN_T1713 小时前
关于网络模型的使用和修改/保存和读取
网络·人工智能·pytorch·python·深度学习·神经网络·机器学习