简介
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
,一个invoke
,bind
方法用于为要代理的真实对象生成代理类。
主要调用的是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
具体的逻辑在ServiceProxy
的invoke
方法里。
借助HttpUtil
调用callRemoteService
方法,传入的remoteClass.values()
代表服务端方法调用的类,传入的method.getName()
代表方法调用的方法名,传入的argTypes
、argValues
分别代表参数类型以及参数值。
remoteClass.values()
和method.getName()
就相当于RPC的Call ID了,可以决定远程调用服务器端的哪个方法,RPC的序列化和反序列化由生成JSON字符串以及解析JSON实现,而网络传输,显而易见,通过OKHttp发送请求。
在Client端发出/getUserInfo
请求后,被MainController
的rpcMain
方法拦截,通过提供的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}
网友评论