JAVA从零手写实现一个简单RPC框架

  • 菜鸟笔记
  • 场景模拟假设
  • 客户端(用户逻辑服务)
  • 服务端(数据查询服务)
  • 运行结果


菜鸟笔记

RPC-远程过程调用

主要用于解决分布式系统中,服务之间的调用问题。远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑

例如:在实际后台开发中,开发者A负责数据查询服务,开发者B负责用户逻辑服务,那么开B需要调用A的用户数据查询服务,所以A.B需要有一个共同约定协调好的接口,A实现该接口,B调用该接口

javax.xml.rpc 依赖 java rpc调用框架_java

场景模拟假设

1.localclient包为一个独立项目,负责用户逻辑服务

2.server包也是独立一个项目,负责数据查询服务

3.公用相同一个类接口UserApi

javax.xml.rpc 依赖 java rpc调用框架_java_02

客户端(用户逻辑服务)

这里需要对动态代理需要一点了解

public class localMain {
    //本地调用远程服务
    public  static  void  main(String args[]){
        UserApi userApi= (UserApi) rpc(UserApi.class);
        User user=userApi.selectById(1);
        System.out.println("本地输出远程调用结果:\n"+user.toString());
    }
    /**
     *  动态创建代理对象
     *  Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
     *  参数1:真实对象的类加载器
     *  参数2:真实对象实现的所有的接口,接口是特殊的类,使用Class[]装载多个接口
     *  参数3: 接口,传递一个匿名内部类对象
     * @param clazz
     * @return
     */
    public  static Object rpc(final Class clazz){
        // Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
        return Proxy.newProxyInstance(clazz.getClassLoader(),new Class[]{clazz},new InvocationHandler() {
            /*
             * @param proxy  代理对象
             * @param method    代理的方法对象
             * @param args  方法调用时参数
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Socket socket=new Socket("127.0.0.1",8888);
                String className=clazz.getName();//api类名
                String  methodName=method.getName();//api 类成员方法名
                Class<?>[] parameterTypes=method.getParameterTypes(); //类成员方法参数类型集合

                ObjectOutputStream objectOutputStream=new ObjectOutputStream(socket.getOutputStream());
                objectOutputStream.writeUTF(className);
                objectOutputStream.writeUTF(methodName);
                objectOutputStream.writeObject(parameterTypes);
                objectOutputStream.writeObject(args);

                ObjectInputStream objectInputStream=new ObjectInputStream(socket.getInputStream());
                Object o=objectInputStream.readObject();
                objectInputStream.close();
                objectOutputStream.close();
                return o;
            }
        });
    }
}

1.这里逻辑是用户逻辑服务需要查数据就需要调用api中方法,所以这里需要用动态代理创建代理对象,动态代理返回一个UserApi类型对象,代理对象调用selectById方法时会自动执行invoke方法
2.这里invoke方法逻辑就是:
它本质是进行Socket通信,而它需要传递信息给数据查询服务,数据查询服务就是根据这些信息调用某个类的某个方法,
只要我们能确定这个类的全限定类名、确定方法名、确定方法的参数类型,给定方法需要的具体参数,通过反射就能实现
数据查询服务得到结果后,将结果序列化写入Socket流中,本地客户端反序列化得到对象即可

服务端(数据查询服务)

1.用户实体类,因为涉及到网络传输,所以用户实体类作为返回结果对象需要实现序列化

public class User implements Serializable {
      private  Integer id;
      private  String username;
      private  String message;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", message='" + message + '\'' +
                '}';
    }
}

2.UserAPi的实现类

public class UserService implements UserApi {

    @Override
    public User selectById(Integer id) {
        User user=new User();
        user.setUsername("张三");
        user.setId(id);
        user.setMessage("张三是胖子");
        System.out.println("UserService调用了selectById()方法查询用户.....");
        return user;
    }
}

3.数据查询服务(main)
注意:用户逻辑服务(客户端)系统中只知道数据查询服务(服务端)的API,即传过来方法类名为接口类名,而服务端需要一个接口API到接口实现的一个映射关系,称为服务的注册,即时UserApi接口---->UserService实现类

public class serverMain {
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket=new ServerSocket(8888);
            System.out.println("启动远程服务监听...");
            //监听客户端发来消息
            while (true){
                Socket socket=serverSocket.accept();
                ObjectInputStream objectInputStream=new ObjectInputStream(socket.getInputStream());
                //读取客户端传输协议包
                String className=objectInputStream.readUTF();
                String methodName=objectInputStream.readUTF();
                Class<?>[] parameterTypes=(Class<?>[])objectInputStream.readObject();
                Object[] arguments= (Object[]) objectInputStream.readObject();

                Class clazz=null;
                //服务注册:API到具体实现的映射
                if(className.equals(UserApi.class.getName())){
                    clazz=UserService.class;
                }
                //传入方法名,方法参数类型获得方法对象
                Method method=clazz.getMethod(methodName,parameterTypes);
                //传入实现类对象,方法参数数组,方法对象执行获得返回结果对象
                Object result=method.invoke(clazz.newInstance(),arguments);

                ObjectOutputStream objectOutputStream=new ObjectOutputStream(socket.getOutputStream());
                objectOutputStream.writeObject(result);
                objectOutputStream.flush();

                objectInputStream.close();
                objectOutputStream.close();
                socket.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果

1.先运行服务端主函数再运行客户端主函数

javax.xml.rpc 依赖 java rpc调用框架_java_03


javax.xml.rpc 依赖 java rpc调用框架_java_04


2.好了,一个简单的RPC框架完成了,这里主要是为了方便理解PRC框架,实际上的运用需要考虑各因素问题的,为了加深自己理解,大牛请忽略本小白~~当然也接受宝贵意见评论~

3.–本文参考了作者Java编程精选,自己理解总结笔记—