手写 RPC (二) 集成 Spring

本文介绍了如何将自定义的RPC框架与Spring集成,包括改造服务端的`rpc-server-provider`,客户端`rpc-client`的修改,以及实现多版本支持。通过自定义`@RpcServiceAnno`注解,配合`ApplicationContextAware`和`InitializingBean`接口,实现服务的自动发布。客户端只需将`RpcProxyClient`交给Spring管理即可。此外,文章还展示了如何在服务端和客户端处理接口的版本迭代。

诗90:10】我们一生的年日是七十岁,若是强壮可到八十岁;但其中所矜夸的不过是劳苦愁烦,转眼成空,我们便如飞而去。


背景

在第一篇「手写RPC(一)基于BIO」我们已经完成了 RPC 的实现,那么服务的发布和调用怎样交给 spring 管理呢?

这正是下面要探讨完成的事情 ?

目录

背景

改造服务端的 rpc-server-provider

客户端 rpc-client 改造

踩蛋   -> 实现多版本支持

rpc-server-api 服务端 api 迭代

 rpc-server-provider 服务端 provicer 迭代

rpc-client 客户端修改

test

源码

 

  • 改造服务端的 rpc-server-provider


在 pom.xml 文件中添加 spring 依赖

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.23.RELEASE</version>
        </dependency>

自定义Rpc注解 @RpcServiceAnno

用以向 spring 容器中注入实例的同时,对外发布接口服务

package com.ddup.rpc.spring;
/**
 * 自定义服务发布注解:
 *     凡是加此注解的,都被发布成对外服务接口
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component // 使用此注解的类,能被spring扫描
public @interface RpcServiceAnno {
    Class<?> value(); //定义使用此注解的服务接口名称
}

业务实现类 HelloServerImpl 

添加 @RpcServiceAnno:[1]交给 spring 管理   [2]容器启动时可 动态处理此注解,对外发布接口

@RpcServiceAnno(IHelloSevice.class)
public class HelloServerImpl implements IHelloSevice {

    @Override
    public String sayHello(String name) {
        System.out.println("reqeust in content : " + name);
        return "hello " + name;
    }
    @Override
    public void saveUser(User user) {
        System.out.println("request in saveUser : "+ user.toString());

    }

现在 HelloServerImpl 已经 spring 容器管理了,那么接下来此服务应该怎么发布,我们应该怎样结合 spring 处理此注解呢?

【1】首先创建 RpcServerPublisher 对 IOC 容器启动时做额外处理

  • 实现 ApplicationContextAware 接口重写 #setApplicationContext 处理添加了 @RpcServiceAnno 的 bean
  • 实现 InitializingBean 接口 重写 #afterPropertiesSet 实现启动服务的过程
package com.ddup.rpc.spring;
/**
 * RPC服务端 集成 spring
 */
public class RpcServerPublisher implements ApplicationContextAware, InitializingBean {

    private int port;
    private Map<String, Object> handlerMap  =  new HashMap<String, Object>();
    /**
     * 缓存线程池
     */
    private ExecutorService executorService = Executors.newCachedThreadPool();

    public RpcServerPublisher(int port) {
        this.port = port;
    }

    /**
     * 启动服务
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(port);
            while (true) {
                Socket socket = serverSocket.accept();
                // 利用多线程处理任务
                executorService.execute(new ProcessorHandler(socket, handlerMap));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 利用容器处理自定义的RPC注解
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(RpcServiceAnno.class);
        if (beansWithAnnotation != null) {
            for (Object bean : beansWithAnnotation.values()) {
                // 处理注解
                RpcServiceAnno rpcServiceAnno = bean.getClass().getAnnotation(RpcServiceAnno.class);
                String serviceName = rpcServiceAnno.value().getName();
                System.out.println("IOC publish service ---> "+serviceName);
                handlerMap.put(serviceName, bean);
            }
        }
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }
}
<think>好的,我现在需要帮助用户了解如何手写实现一个RPC框架。根据用户提供的引用资料,我需要整理出一个清晰的步骤,并确保符合格式要求,比如数学表达式的正确使用和引用标识的添加。 首先,用户提到的引用[2]详细说明了RPC的核心实现原理,包括自定义XML配置、Netty通信、JDK动态代理等。这应该作为基础来构建步骤。我需要分步骤解释每个组件的作用,比如注册中心、通信层、代理机制等。 然后,引用[1]提到了配置Nacos地址和启动代码,这说明注册中心的选择和配置是关键步骤。我需要强调服务注册与发现的部分,可能涉及服务提供者和消费者如何通过注册中心交互。 接下来,引用[3]讨论了整合SpringSpringBoot,这部分需要说明如何将RPC框架与现有框架集成,比如自定义命名空间和启动类。这可能会涉及XML配置和Spring的Bean处理。 引用[4]提到了项目中常见的问题,可能在实现过程中会遇到序列化、网络通信或动态代理的问题,需要在注意事项里提醒用户。 接下来,我需要确保每个部分的结构清晰,使用中文回答,并且在适当的地方添加引用标识。例如,在讲注册中心时引用[1],在讲Netty时引用[2]。 同时,用户要求生成相关问题,我需要根据内容提出相关问题,比如性能优化、扩展点、与现有框架的整合等。 最后,检查LaTeX语法是否正确,比如数学表达式是否用$...$包裹,独立公式是否用$$...$$。例如,在提到负载均衡算法时,可能需要公式,但当前内容可能不需要数学表达式,所以暂时不添加。但需要确认是否有遗漏。 现在整理步骤: 1. RPC框架的核心组件:客户端代理、通信协议、序列化、服务注册与发现、服务端处理。引用[2]中的内容。 2. 具体实现步骤:通信层(Netty)、动态代理、服务注册(Nacos)、序列化选择、整合Spring。 3. 示例代码展示关键部分,如代理类和Netty处理。 4. 注意事项,引用[4]中的常见问题。 5. 相关问题生成。 确保每个部分自然添加引用,例如在提到服务注册时引用[1]和[2],在整合Spring时引用[3]。 现在开始组织回答,确保结构清晰,步骤明确,符合用户的要求。</think>### 手写实现RPC框架的原理与步骤 RPC(远程过程调用)框架的核心目标是让本地调用远程服务像调用本地方法一样简单。以下是手写实现RPC框架的关键步骤与原理详解: --- #### 一、RPC框架的核心组件 1. **客户端代理**:通过动态代理技术(如JDK Proxy)生成接口的代理对象,拦截方法调用并封装为网络请求[^2]。 2. **通信协议**:定义数据传输格式(如请求头、序列化方式、状态码),通常基于TCP协议实现高效通信[^2]。 3. **序列化/反序列化**:将对象转换为进制数据(如JSON、Protobuf、Hessian),确保跨网络传输后的可还原性[^4]。 4. **服务注册与发现**:通过注册中心(如Nacos)管理服务地址,实现服务提供者与消费者的动态解耦[^1]。 5. **服务端处理**:监听请求并反射调用具体方法,将结果返回给客户端。 --- #### 、具体实现步骤 ##### 1. **通信层实现(Netty)** - **服务端**:使用Netty监听端口,处理客户端请求,解码请求数据并调用本地方法。 - **客户端**:通过Netty发送请求,等待响应后返回结果。 ```java // 示例:Netty服务端初始化 ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ch.pipeline().addLast(new RpcDecoder(), new RpcEncoder(), new ServerHandler()); } }); ``` 引用[2]提到Netty的高性能特性使其成为通信层的理想选择。 ##### 2. **动态代理(JDK Proxy)** 客户端通过代理类将方法调用转为网络请求: ```java public class ClientProxy implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) { // 封装请求并发送至服务端 return nettyClient.sendRequest(RpcRequest.build(method, args)); } } ``` 代理类隐藏了底层通信细节,使调用透明化。 ##### 3. **服务注册与发现** - **服务提供者**:启动时向注册中心注册自身地址(如`192.168.1.1:8080`)。 - **服务消费者**:调用时从注册中心获取可用地址列表,并通过负载均衡算法(如轮询)选择目标服务。 ##### 4. **序列化选择** 常用序列化方案对比: | 方案 | 性能 | 可读性 | 跨语言支持 | |---------|----|-----|-------| | JSON | 中 | 高 | 是 | | Protobuf | 高 | 低 | 是 | | Hessian | 中 | 中 | 是 | 建议根据场景选择,如高并发场景优先Protobuf。 ##### 5. **整合Spring** - **自定义XML配置**:定义`<rpc:service>`标签,解析并注册Bean。 - **启动类**:通过`@RpcReference`注解注入代理对象。 ```xml <!-- 示例:自定义RPC服务配置 --> <rpc:service interface="com.example.UserService" ref="userServiceImpl"/> ``` 引用[3]提到通过Spring Schema扩展实现配置解析。 --- #### 三、注意事项 1. **网络超时与重试**:需设置超时机制,避免因网络问题阻塞线程。 2. **异步调用**:支持Future或回调函数提升吞吐量。 3. **服务治理**:实现熔断、限流等容错机制(可参考Dubbo设计)。 ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值