一个简单的自定义RPC框架实现demo代码详解-2022

一、基本思路:

   首先,根据rpc的调用逻辑,需要实现 " 像调用本地方法一样,调用远程方法“,那么我们需要考虑以下几点:

  • 基于共享接口还是 IDL?
      我们采用的是共享接口;
  • 动态代理 or AOP?
      我们采用动态代理,来代理要实现的类
  • 序列化用什么?
      我们采用json报文的形式进行交互
  • 文本 or 二进制?
      文本
  • 基于 TCP 还是 HTTP?
      我们采用的是http,tcp可以用netty
  • 服务端如何查找实现类?
       我们用spring 的bean工厂来获取
  • 异常处理
       可以自定义全局异常,本代码并没有完善

二、代码演示:

1.项目概括

    举个例子,老王在外面出差,身份证忘带了,打电话让老婆把身份证拍个照片发给他。那么这个行为就是rpc远程调用。本来如果老王在家,就可以自己拍照。但是现在人在外面,打电话让老婆拍身份证照片,就是之前说的rpc主旨 ” 像调用本地方法一样,调用远程方法“。
在这里插入图片描述

a、 rpc-consumer(消费者模块):
   项目角色中的消费者,主要进行远程调用方。例如上面那个例子中出差的老王,他要做的一些行为都是消费者行为。

b、 rpc-core(自定义rpc核心模块):
   整个框架的核心骨架部分,大脑核心。

c、 rpc-provider(生产者模块):
   项目角色中的生产者,主要是被远程调用方。例如上面那个例子中的老婆,在案例中为老王提供服务。

d、 rpc-demo-api(api的模块):
   该模块主要包括接口层和实体类,为项目提供可远程调用的功能方法。



2.代码rpc-consumer(消费者模块)

在这里插入图片描述

① .pom依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>rpc-consumer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>rpc-consumer</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>rpc-core</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>rpc-demo-api</artifactId>
            <version>${project.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>


②. application.properties配置端口号

server.port=8000


③. RpcConsumerApplication在启动类模拟消费情况

package com.example.rpcconsumer;

import com.example.rpc.client.Rpcfx;
import com.example.rpc.demo.api.Order;
import com.example.rpc.demo.api.OrderService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RpcConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(RpcConsumerApplication.class, args);

        //调用
        OrderService orderService =  Rpcfx.create(OrderService.class, "http://localhost:8080/");
        Order orderById = orderService.findOrderById(1);
        System.out.println("实际调用结果:");
        System.out.println(orderById);
    }

}



2.代码rpc-core(自定义rpc核心模块)

我们底层采用:

  • 动态代理来代理远程访问的类
  • okhttp进行远程调用;
  • 交互采用json报文

项目



① .pom依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>rpc-core</artifactId>
    <version>1.0-SNAPSHOT</version>




    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.3.RELEASE</version>
        <relativePath/>
    </parent>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.6.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>

        </plugins>

    </build>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.70</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
        </dependency>

        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>3.12.2</version>
        </dependency>


        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-client</artifactId>
            <version>5.1.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>5.1.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>


</project>


②. api 包自定义的一些类RpcRequest、RpcResponse、RpcResolver

  • RpcRequest
package com.example.rpc.api;

import lombok.Data;

/**
 * 请求实体
 */
@Data
public class RpcRequest {

    /**
     * 哪个接口类
     */
    private String serviceClass;
    /**
     * 哪个方法
     */
    private String method;


    /**
     * 参数
     */
    private Object [] params;

}
  • RpcResponse
package com.example.rpc.api;

import lombok.Data;

/**
 * 响应实体
 */
@Data
public class RpcResponse {
    /**
     * 响应状态
     */
    private String status;

    /**
     * 返回值
     */
    private Object result;


    /**
     * xinxi
     */
    private String message;

    private Exception exception;

}
  • RpcResolver
package com.example.rpc.api;

/**
 * 用与真实对象获取
 */
public interface RpcResolver {

    /**
     * 获取bean
     * @param serviceClass
     * @return
     */
    Object resolve(String serviceClass);

    /**
     * 获取bean+泛型
     * @param aClass
     * @param <T>
     * @return
     */
    <T>T resolveT(Class<T> aClass);
}



③. client包自定义主要客户端用的请求,代理请求调用生产者并处理Rpcfx

  • Rpcfx
package com.example.rpc.client;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.ParserConfig;
import com.example.rpc.api.RpcRequest;
import com.example.rpc.api.RpcResponse;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;

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

/**
 * RPC核心板块
 */
public class Rpcfx {



    public static <T>T create(final Class<T> tClass,final String url) {
        //用动态代理替换
        return (T)Proxy.newProxyInstance(Rpcfx.class.getClassLoader(),new Class[]{tClass},new RpcHandler(tClass, url));
    }


    /**
     * 自定义处理类,动态代理实际运行的类
     */
    public static class RpcHandler implements InvocationHandler{
        //        public final MediaType JSONTYPE = MediaType.get("application/json; charset=utf-8");
        public static MediaType JSONTYPE = MediaType.parse("application/json;charset=UTF-8");

        private final Class<?> serviceClass;
        private final String url;

        public RpcHandler(Class<?> serviceClass, String url) {
            this.serviceClass = serviceClass;
            this.url = url;
        }

        //实际调用的方法
        public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
            //建立一个请求体
            RpcRequest rpcRequest = new RpcRequest();
            rpcRequest.setMethod(method.getName());
            rpcRequest.setParams(params);
            rpcRequest.setServiceClass(this.serviceClass.getName());
            //请求
            RpcResponse rpcResponse = post(rpcRequest,url);
            if(!"200".equals(rpcResponse.getStatus())){
                // 这里判断response.status,处理异常
                //failed
                throw  rpcResponse.getException();
            }

            // 考虑封装一个全局的RpcException
            //返回具体响应内容
            String s = rpcResponse.getResult().toString();
            //开启autoType
            ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
            return JSON.parse(s);
        }

        /**
         * 采用OKHttp远程调用
         * @param rpcRequest
         * @param url
         * @return
         */
        private RpcResponse post(RpcRequest rpcRequest, String url) throws IOException {
            String reqJson = JSON.toJSONString(rpcRequest);
            System.out.println("远程请求参数: "+reqJson);

            OkHttpClient okHttpClient = new OkHttpClient();
            Request request =  new Request.Builder()
                    .url(url)
                    .post(RequestBody.create(JSONTYPE,reqJson))
                    .build();
            String respJson = okHttpClient.newCall(request).execute().body().string();
            System.out.println("远程调用返回结果:"+respJson);
            return JSON.parseObject(respJson,RpcResponse.class);
        }
    }
}


④. server包自定义配合服务端,解析rpc远程调用的底层实现类。RpcInvoker

  • RpcInvoker
package com.example.rpc.server;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.example.rpc.api.RpcRequest;
import com.example.rpc.api.RpcResolver;
import com.example.rpc.api.RpcResponse;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * 生产者端进行反射调用
 */
public class RpcInvoker {
    private RpcResolver resolver;

    public RpcInvoker(RpcResolver resolver){
        this.resolver = resolver;
    }

    public RpcResponse invoke(RpcRequest request) {
        RpcResponse response = new RpcResponse();
        String serviceClass = request.getServiceClass();
        try {
            // 作业1:改成泛型和反射
            //直接用反射
//            Object result = invokeOne(serviceClass,request);
            Object result = invokeTwo(serviceClass,request);
            // 两次json序列化能否合并成一个
            response.setResult(JSON.toJSONString(result, SerializerFeature.WriteClassName));
//            response.setResult(JSON.toJSONString(result));
            response.setStatus("200");
            return response;
        } catch ( Exception e) {

            // 3.Xstream

            // 2.封装一个统一的RpcfxException
            // 客户端也需要判断异常
            e.printStackTrace();
            response.setException(e);
            response.setStatus("710");
            return response;
        }

    }

    /**
     * 反射加泛型
     * @param serviceClass
     * @param request
     * @return
     * @throws ClassNotFoundException
     */
    private Object invokeTwo(String serviceClass, RpcRequest request) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException {
        Class<?> aClass = Class.forName(serviceClass);
        Object obj = resolver.resolveT(aClass);
        Method method = resolveMethodFromClass(aClass, request.getMethod());
        return method.invoke(obj,request.getParams());
    }

    private Object invokeOne(String serviceClass, RpcRequest request) throws InvocationTargetException, IllegalAccessException {
        Object service = resolver.resolve(serviceClass);//this.applicationContext.getBean(serviceClass);
        Method method = resolveMethodFromClass(service.getClass(), request.getMethod());
        return method.invoke(service, request.getParams()); // dubbo, fastjson,
    }

    private Method resolveMethodFromClass(Class<?> klass, String methodName) {
        return Arrays.stream(klass.getMethods()).filter(m -> methodName.equals(m.getName())).findFirst().get();
    }
}



3. 代码 rpc-provider(生产者模块)

在这里插入图片描述



①. pom依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.example</groupId>
    <artifactId>rpc-provider</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>rpc-provider</name>
    <description>Demo project for Spring Boot</description>



    <properties>
        <java.version>1.8</java.version>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>rpc-core</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>rpc-demo-api</artifactId>
            <version>${project.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>



②. application.properties

server.port= 8080


③. DemoResolver 实现RpcResolver,ApplicationContextAware接口,目的就是获取目标bean

package com.example.rpcprovider;

import com.example.rpc.api.RpcResolver;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;


/**
 * 获取生产者的bean
 * Created by IntelliJ IDEA.
 * @author NJ
 * @create 2022/3/10 17:26
 */
@Service
public class DemoResolver implements RpcResolver, ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Override
    public Object resolve(String serviceClass) {
        return this.applicationContext.getBean(serviceClass);
    }

    @Override
    public <T> T resolveT(Class<T> aClass) {
        return this.applicationContext.getBean(aClass);
    }
}


④. OrderServiceImpl 生产者的本地service实现方法,具体执行者

package com.example.rpcprovider;


import com.example.rpc.demo.api.Order;
import com.example.rpc.demo.api.OrderService;
import org.springframework.stereotype.Service;

@Service
public class OrderServiceImpl implements OrderService {

    @Override
    public Order findOrderById(int id) {
        return new Order(id, "hello rpc" + System.currentTimeMillis(), 9.9f);
    }
}


④. RpcProviderApplication 生产者的给消费者提供访问的入口

package com.example.rpcprovider;

import com.example.rpc.api.RpcRequest;
import com.example.rpc.server.RpcInvoker;
import com.example.rpc.api.RpcResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@SpringBootApplication
public class RpcProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(RpcProviderApplication.class, args);
    }

    @Autowired
    RpcInvoker invoker;

    @PostMapping("/")
    private Object ppp(@RequestBody RpcRequest request){
        return invoker.invoke(request);
    }

    @Bean
    public RpcInvoker creatBean(@Autowired RpcResolver resolver){
        return new RpcInvoker(resolver);
    }
}

4. 代码 rpc-demo-api(api的模块)

在这里插入图片描述


①. pom依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>rpc-core</artifactId>
        <version>1.0-SNAPSHOT</version>
        <!--		<relativePath/> &lt;!&ndash; lookup parent from repository &ndash;&gt;-->
    </parent>


    <groupId>com.example</groupId>
    <artifactId>rpc-demo-api</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>rpc-demo-api</name>

    <properties>
        <java.version>1.8</java.version>
    </properties>

</project>


②. Order业务实体

package com.example.rpc.demo.api;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {

    private int id;

    private String name;

    private float amount;

}


③. OrderService业务接口

package com.example.rpc.demo.api;

/**
 * 接口类
 */
public interface OrderService {

    Order findOrderById(int id);
}

5. 运行演示结果

  先启动生产者服务,再启动消费者,即可看到消费者打印的日志,如下:

2022-03-11 11:08:39.191  INFO 23984 --- [           main] c.e.rpcconsumer.RpcConsumerApplication   : Starting RpcConsumerApplication using Java 1.8.0_121 on BY with PID 23984 (E:\nj\geek_learn\src\main\java\com\nj\learn\rpc\rpc-consumer\target\classes started by rvwrb in E:\nj\geek_learn)
2022-03-11 11:08:39.196  INFO 23984 --- [           main] c.e.rpcconsumer.RpcConsumerApplication   : No active profile set, falling back to default profiles: default
2022-03-11 11:08:40.138  INFO 23984 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8000 (http)
2022-03-11 11:08:40.148  INFO 23984 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-03-11 11:08:40.148  INFO 23984 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.52]
2022-03-11 11:08:40.270  INFO 23984 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-03-11 11:08:40.270  INFO 23984 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1004 ms
2022-03-11 11:08:40.665  INFO 23984 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8000 (http) with context path ''
2022-03-11 11:08:40.677  INFO 23984 --- [           main] c.e.rpcconsumer.RpcConsumerApplication   : Started RpcConsumerApplication in 2.272 seconds (JVM running for 3.884)
远程请求参数: {"method":"findOrderById","params":[1],"serviceClass":"com.example.rpc.demo.api.OrderService"}
远程调用返回结果:{"status":"200","result":"{\"@type\":\"com.example.rpc.demo.api.Order\",\"amount\":9.9,\"id\":1,\"name\":\"hello rpc1646968121630\"}","message":null,"exception":null}
实际调用结果:
Order(id=1, name=hello rpc1646968121630, amount=9.9)



三、作者有话说:

  作者自己也是通过网上课程学习的rpc底层框架技术,整个文档起到笔记作用,其中带一些自己的理解,文中可能会有一些不专业,还望大家阅读之后能够指出。谢谢大家,一起进步。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值