简单实现Java手写RPC框架

RPC为remote process control,远程过程控制,RPC框架即为帮一个服务调用另一个服务的框架,以下为RPC框架的简单代码实现

创建项目

首先创建一个项目和四个模块
在这里插入图片描述
分别为服务消费者模块,服务提供者模块,服务接口模块,和RPC模块

在服务接口模块中创建一个服务接口

package com.slqf.service;

public interface HelloService {
    String sayHello(String name);
}

然后在消费者模块和生产者模块中都导入这个模块,也顺便导入RPC模块

<dependencies>
    <dependency>
        <groupId>org.example</groupId>
        <artifactId>Producer-common</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

    <dependency>
        <groupId>org.example</groupId>
        <artifactId>SLQF-rpc</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

在生产者模块中实现这个服务接口

package com.slqf.serviceIml;

import com.slqf.service.HelloService;

public class HelloServiceIml implements HelloService {
    @Override
    public String sayHello(String name) {
        return "hello:" + name;
    }
}

编写RPC框架

在消费者模块中,不能通过创建HelloServiceImp对象来调用Provider中的sayHello方法,那就只能通过网络请求的方式来调用,接收网络请求的方式有很多
Jetty,Tomcat,Netty,或者直接用Socket,这里选择使用的是Tomcat
在RPC模块中导入Tomcat依赖以及IO工具包

    <dependencies>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>9.0.79</version>
        </dependency>

        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.1</version>
        </dependency>
    </dependencies>

需要在Provider服务启动时,启动Tomcat,在RPC模块中编写一个Tomcat启动方法,方法中的参数为IP地址和绑定的端口号

package com.slqf.protocol;

import org.apache.catalina.*;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.startup.Tomcat;

public class HttpServer {
    public void start(String hostName, int port) {
        Tomcat tomcat = new Tomcat();

        Server server = tomcat.getServer();
        Service service = server.findService("Tomcat");

        Connector connector = new Connector();
        connector.setPort(port);

        Engine engine = new StandardEngine();
        engine.setDefaultHost(hostName);

        Host host = new StandardHost();
        host.setName(hostName);

        String contextPath = "";
        Context context = new StandardContext();
        context.setPath(contextPath);
        context.addLifecycleListener(new Tomcat.FixContextListener());

        host.addChild(context);
        engine.addChild(host);

        service.setContainer(engine);
        service.addConnector(connector);

		// 将所有请求都交给dispatcher处理
        tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet());
        context.addServletMappingDecoded("/*", "dispatcher");

        try {
            tomcat.start();
            tomcat.getServer().await();
        } catch (LifecycleException e) {
            e.printStackTrace();
        }
    }
}

其中使用的dispatcher为自定义的DispatcherServlet,用来处理收到的网络消息
同样在RPC模块中编写DispatcherServlet

package com.slqf.protocol;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class DispatcherServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    	// 抽象为Hander类
        new HttpServerHandler().handler(req, resp);
    }
}

将处理方式抽象为一个类,提升框架可扩展性,如果还要处理其他类型的网络消息,可以增加另外的Handler类,随后编写客户端发送的Invocation消息类

package com.slqf.common;

import java.io.Serializable;

public class Invocation implements Serializable {
    // 接口名称
    private String interfaceName;
    // 需要调用的方法名
    private String methodName;
    // 方法中参数类型
    private Class[] types;
    // 方法的参数
    private Object[] args;

    public String getInterfaceName() {
        return interfaceName;
    }

    public void setInterfaceName(String interfaceName) {
        this.interfaceName = interfaceName;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Class[] getTypes() {
        return types;
    }

    public void setTypes(Class[] types) {
        this.types = types;
    }

    public Object[] getArgs() {
        return args;
    }

    public void setArgs(Object[] args) {
        this.args = args;
    }

    public Invocation(String interfaceName, String methodName, Class[] types, Object[] args) {
        this.interfaceName = interfaceName;
        this.methodName = methodName;
        this.types = types;
        this.args = args;
    }
}

之后是发送网络消息的客户端方法,参数为IP地址,Tomcat服务器端口号,消息Invocation

package com.slqf.protocol;

import com.slqf.common.Invocation;
import org.apache.commons.io.IOUtils;

import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class HttpClient {
    public String send(String host, int port, Invocation invocation) {
        String result = null;
        try {
            URL url = new URL("http", host, port, "/");
            HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();

            httpConnection.setRequestMethod("POST");
            httpConnection.setDoOutput(true);

            OutputStream out = httpConnection.getOutputStream();
            ObjectOutputStream outStream = new ObjectOutputStream(out);
            outStream.writeObject(invocation);
            outStream.flush();
            outStream.close();

            InputStream inputStream = httpConnection.getInputStream();
            result =  IOUtils.toString(inputStream);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}

当收到客户端发送网络消息后,Tomcat默认调用我们配置的DispatcherServlet,编写其中处理消息的Handle

package com.slqf.protocol;

import com.slqf.common.Invocation;
import com.slqf.register.LocalRegister;
import org.apache.commons.io.IOUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ObjectInputStream;
import java.lang.reflect.Method;

public class HttpServerHandler {
    public void handler(HttpServletRequest request, HttpServletResponse response){
        try {
            // 获取客户端传过来的Invocation消息
            Invocation invocation = (Invocation) new ObjectInputStream(request.getInputStream()).readObject();
            String interfaceName = invocation.getInterfaceName();
            String methodName = invocation.getMethodName();
            // 根据接口名称,获取想要调用的实现类
            Class clazz = LocalRegister.get(interfaceName);
            Method method = clazz.getMethod(methodName, invocation.getTypes());
            // 利用反射执行消费者想要调用的方法
            String result = (String)method.invoke(clazz.newInstance(), invocation.getArgs());
            // 返回方法的执行结果
            IOUtils.write(result, response.getOutputStream());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这就是处理消息的类,也是调用服务关键所在,以下代码段中的LocalRegister类,是一个本地注册类,里面保存了接口与对应实现类的映射,保证调用的是想要的实现类的方法,以下是LocalRegister的代码

package com.slqf.register;

import java.util.*;

public class LocalRegister {
    private static Map<String, Class> map = new HashMap<>();

    public static void register(String interfaceName, Class implClass) {
        map.put(interfaceName, implClass);
    }

    public static Class get(String ingerfaceName) {
        return map.get(ingerfaceName);
    }
}

执行和测试

现在RPC框架的主要架构已经编写完成,可以进行测试一下,在Provider服务模块中启动Tomcat并且注册服务

package com.slqf;

import com.slqf.protocol.HttpServer;
import com.slqf.register.LocalRegister;
import com.slqf.service.HelloService;
import com.slqf.serviceIml.HelloServiceIml;

public class Provider {
   public static void main(String[] args) {
       // 注册服务
       LocalRegister.register(HelloService.class.getName(), HelloServiceIml.class);

       HttpServer server = new HttpServer();
       server.start("localhost",8080);
   }
}

启动的效果如下
在这里插入图片描述
然后再Consumer服务中尝试调用Provider服务中的sayHello方法

package com.slqf;

import com.slqf.common.Invocation;
import com.slqf.protocol.HttpClient;
import com.slqf.service.HelloService;

public class Consumer {
    public static void main(String[] args) {
        HttpClient client = new HttpClient();
        Invocation invocation = new Invocation(HelloService.class.getName(),
                "sayHello", new Class[]{String.class}, new Object[]{"三里清风"});
        String result = client.send("localhost", 8080, invocation);
        System.out.println(result);
    }
}

执行结果如下,调用成功
在这里插入图片描述

使用代理类优化代码

虽然说编写到这里,已经实现了简单的RPC框架,但是不够优雅,RPC框架通常都是返回一个接口的代理对象,然后执行的代理对象的方法来实现服务调用,接下来就是编写一个代理类来优化代码,代理类当然也放在RPC模块中

package com.slqf.proxy;

import com.slqf.common.Invocation;
import com.slqf.protocol.HttpClient;

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

public class ProxyFactory {
    public static <T>T getProxy(Class interfaceClass) {
        Object proxy = Proxy.newProxyInstance(ProxyFactory.class.getClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 使用反射来创建消息对象
                Invocation invocation = new Invocation(interfaceClass.getName(), method.getName(),
                        method.getParameterTypes(), args);
                HttpClient client = new HttpClient();
                String result = client.send("localhost", 8080, invocation);
                return result;
            }
        });
        return (T)proxy;
    }
}

有没有发现很多地方使用了反射,可以说绝大多数框架的底层都使用了反射
之后在Consumer中就可以直接创建HelloService的代理对象来调用Provider中的服务了

package com.slqf;

import com.slqf.proxy.ProxyFactory;
import com.slqf.service.HelloService;

public class Consumer {
    public static void main(String[] args) {
        HelloService helloService = ProxyFactory.getProxy(HelloService.class);
        String result = helloService.sayHello("三里清风AAAA");
        System.out.println(result);
    }
}

执行代码,调用服务成功
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值