美文网首页
一个简单的RPC远程调用例子

一个简单的RPC远程调用例子

作者: 坠尘_ae94 | 来源:发表于2020-08-31 08:54 被阅读0次

简介

RPC是远程过程调用(Remote Procedure Call)的缩写形式。SAP系统RPC调用的原理其实很简单,有一些类似于三层构架的C/S系统,第三方的客户程序通过接口调用SAP内部的标准或自定义函数,获得函数返回的数据进行处理后显示或打印。

关于RPC,B乎有个蛮好的回答:谁能用通俗的语言解释一下什么是 RPC 框架?,可以去康康。

准备

InvocationHandler

动态代理

Processes a method invocation on a proxy instance and returns the result. This method will be invoked on an invocation handler when a method is invoked on a proxy instance that it is associated with.

它只有一个invoke方法:

public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;

这个方法定义了代理类的逻辑。

例子

比如有一个讲礼貌的IHello接口,定义了两个方法:

    void sayHello(String name);

    void sayGoodbye(String name);

HelloImpl实现了讲礼貌的IHello接口:

    @Override
    public void sayHello(String name) {
        System.out.println("Hello " + name);
    }

    @Override
    public void sayGoodbye(String name) {
        System.out.println(name + " GoodBye!");
    }

现在,需求变了,我们不仅需要IHello会讲礼貌,还需要IHello在讲礼貌之前先记录下IHello准备讲礼貌了。

可以类比下Spring的通知,比如前置通知,拦截指定方法并执行逻辑。

这个可以通过设计模式配合多态实现,也可以借助InvocationHandler实现。

这里就借助InvocationHandler实现下:


public class HelloDynamicProxy implements InvocationHandler {

    private Object delegete;

    public Object bind(Object delegete) {
        this.delegete = delegete;
        return Proxy
                .newProxyInstance(
                        this.delegete.getClass().getClassLoader(),
                        this.delegete.getClass().getInterfaces(),
                        this
                );
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        Object result = null;
        try {
            System.out.println("Before Hello");
//            反射
            result = method.invoke(this.delegete, args);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    public static void main(String[] args) {
        HelloDynamicProxy helloDynamicProxy = new HelloDynamicProxy();
        HelloImpl hello = new HelloImpl();
        IHello iHello = (IHello) helloDynamicProxy.bind(hello);
        iHello.sayHello("Liangzai");
        iHello.sayGoodbye("Liangzai");
        System.out.println(iHello.getClass().getName());
    }
}

结果:

Before Say
Hello Liangzai

解释

HelloDynamicProxy中主要有两个方法,一个bind,一个invokebind方法用于为要代理的真实对象生成代理类。

主要调用的是Proxy的静态方法newProxyInstance

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)

该方法接收三个参数:

  • loader:定义代理类的ClassLoader
  • interfaces:代理类需要实现的接口集
  • h:“the invocation handler to dispatch method invocations to”(我说不好,这是源码上的注释)

invoke方法,如前所述,代理类的具体行为。这里就是在IHello接口打招呼前打印一句话,再通过method.invoke调用被代理方法。

开始

本示例主要介绍Github看到的一个开源项目代码:EasyRPC

该项目由两个模块构成,一个Client端,一个Server端。用户访问Client接口,Client远程过程调用Server端的方法并返回。

文件:


image-20200826214528070

加载

由于是SpringBoot项目,实例均由Spring容器管理,而Client只有两个接口,所以我们将代理对类存入容器。

Spring的FactoryBean一般用来创建比较复杂的类。

Interface to be implemented by objects used within a {@link BeanFactory} which are themselves factories for individual objects. If a bean implements this interface, it is used as a factory for an object to expose, not directly as a bean instance that will be exposed itself.

ServiceFactory重写getObject方法,利用InvocationHandler生成代理类。

@Override
public T getObject() {
    InvocationHandler handler = new ServiceProxy<>(interfaceType);
    return (T) Proxy.newProxyInstance(interfaceType.getClassLoader(),
            new Class[]{interfaceType}, handler);
}

ServiceProxy则是InvocationHandler实现类,invoke方法包括具体的调用过程,可看此处.

        RemoteClass remoteClass = method.getDeclaringClass().getAnnotation(RemoteClass.class);
        if (remoteClass == null) {
            throw new Exception("远程类标志未指定");
        }

        List<String> argTypeList = new ArrayList<>();
        if (args != null) {
            for (Object obj : args) {
//                获取所有参数的类名
                argTypeList.add(obj.getClass().getName());
            }
        }

        String argTypes = JSON.toJSONString(argTypeList);
        String argValues = JSON.toJSONString(args);

        Result result = HttpUtil.callRemoteService(remoteClass.value(), method.getName(), argTypes, argValues);

        if (result.isSuccess()) {
            return JSON.parseObject(result.getResultValue(), Class.forName(result.getResultType()));
        } else {
            throw new Exception("远程调用异常:" + result.getMessage());

        }

关于@RemoteClass

@RemoteClass("com.github.yeecode.easyrpc.server.service.UserService")
public interface UserService {}

ServiceBeanDefinitionRegistry实现了BeanDefinitionRegistryPostProcessor,ResourceLoaderAware,ApplicationContextAware,Bean注册:

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    Set<Class<?>> clazzSet = scannerPackages("com.github.yeecode.easyrpc.client.remoteservice");
    clazzSet.stream().filter(Class::isInterface).forEach(x -> registerBean(registry, x));
}

实际注册的就是上面说的ServiceFactory.

BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
...
definition.setBeanClass(ServiceFactory.class);

远程调用

这里介绍一个访问过程 :127.0.0.1:12310/getUserInfo

具体的逻辑在ServiceProxyinvoke方法里。

借助HttpUtil调用callRemoteService方法,传入的remoteClass.values()代表服务端方法调用的类,传入的method.getName()代表方法调用的方法名,传入的argTypesargValues分别代表参数类型以及参数值。

remoteClass.values()method.getName()就相当于RPC的Call ID了,可以决定远程调用服务器端的哪个方法,RPC的序列化和反序列化由生成JSON字符串以及解析JSON实现,而网络传输,显而易见,通过OKHttp发送请求。

在Client端发出/getUserInfo请求后,被MainControllerrpcMain方法拦截,通过提供的methodName,argTypes找到对应的Method,方便后续反射调用。

Method method = clazz.getMethod(methodName, argClassArray);

之后返回JSON结果:

{"sex":0,"name":"name","id":1,"schoolName":"Sunny School","email":"name@sample.com","age":19}

参考

  1. 远程过程调用
  2. Java动态代理之InvocationHandler
  3. java的动态代理机制详解
  4. RPC简介及框架选择
  5. EasyRPC
  6. Spring FactoryBean应用

相关文章

网友评论

      本文标题:一个简单的RPC远程调用例子

      本文链接:https://www.haomeiwen.com/subject/jwzysktx.html