首先看图
我们希望在客户端中调用接口EchoService的echo方法,但是客户端项目并没有加入实现类EchoServiceImpl的依赖,接口甚至无法实例化,更别提调用方法了。
那么没有实体类的接口如何实例化呢,答案其实很简单,就是----
自己写一个
当然不能自己写,这里要用的是动态代理技术。定义一个代理方法如下:
public <T> T clientProxy(final Class<T> interfaceCls) {
return (T) Proxy.newProxyInstance(interfaceCls.getClassLoader(), new Class[]{interfaceCls}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return "Hello Proxy";
}
});
}
然后,EchoService接口就可以被实例化了,如下:
RpcClientProxy rpcClientProxy = new RpcClientProxy();
EchoService echoService = rpcClientProxy.clientProxy(EchoService.class);
String echo = echoService.echo("Tom");
但是现在的代理方法invoke永远只会返回 Hello Proxy,显然没什么用。为了调用到远在另一台服务器的EchoServiceImpl的真实echo方法,需要借助网络通信。但是另一台服务器需要哪些信息才能确定调用的是哪个类的哪个方法呢,分析如下:
信息 | 如何取得 |
---|---|
类名 | method.getDeclaringClass().getName() |
方法名 | method.getName() |
参数表 | Object[] args method.getParameterTypes() |
按以上信息定义一个POJO类用于封装传输数据
public class RpcRequest implements Serializable {
private String className;
private String methodName;
private Object[] params;
private Class[] types;
public RpcRequest(String className, String methodName, Object[] params, Class[] types) {
this.className = className;
this.methodName = methodName;
this.params = params;
this.types = types;
}
该类需要序列化后通过网络发送出去,所以必须实现Serializable接口,然后用ObjectOutputStream发送。
接下来就是完善代理类,把请求封装成RpcRequest实例发送到服务器端,代码如下:
public <T> T clientProxy(final Class<T> interfaceCls) {
return (T) Proxy.newProxyInstance(interfaceCls.getClassLoader(), new Class[]{interfaceCls}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try(Socket socket = new Socket("localhost", 9999);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream())) {
RpcRequest rpcRequest = new RpcRequest(method.getDeclaringClass().getName(), method.getName(), args, method.getParameterTypes());
objectOutputStream.writeObject(rpcRequest);
objectOutputStream.flush();
return objectInputStream.readObject();
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
});
}
为了简单,服务器端使用BIO开启Socket监听9999端口,代码如下:
try(ServerSocket serverSocket = new ServerSocket(port)) {
while(true) {
Socket socket = serverSocket.accept();
//开启一个线程处理socket的输入输出
}
} catch (IOException e) {
e.printStackTrace();
}
对于Socket的输入即客户端发送的序列化后的RpcRequest,服务器端用ObjectInputStream读取后还原,然后通过反射调用EchoServiceImpl的echo方法
RpcRequest rpcRequest = (RpcRequest) objectInputStream.readObject();
Class clazz = Class.forName(rpcRequest.getClassName());
Method method = clazz.getMethod(rpcRequest.getMethodName(), rpcRequest.getTypes());
Object result = method.invoke(service, rpcRequest.getParams());
完整代码
链接:https://pan.baidu.com/s/157vJBZg5cr9eTghyuM4OTA
提取码:b548
这个简陋的演示包含了RPC框架的基本原理,即代理、序列化和Socket通信。作为RPC No.1的Dubbo,在这三个方面都选择了更先进的技术,例如使用javassist技术生成代理对象,支持protobuf、hession等duoz多种序列化协议,使用Netty框架实现网络传输,但是基本原理是相同的。