不使用其他框架实现RPC远程调用
回顾RPC框架
节点 | 角色说明 |
---|---|
Provider | 提供远程服务的服务提供方 |
Consumer | 调用远程服务的服务消费方 |
Registry | 服务注册与发现的注册中心 |
Dubbo最重要的三个角色就是服务提供方、服务消费方、注册中心。
下面我们通过Java原生API实现远程调用,不使用任何第三方框架!
服务提供方
package rpc.provider;
/**
* 服务提供方 接口
*
* @author zab
* @date 2020-05-16 17:51
*/
public interface ProviderService {
String testMethod(String ping);
}
package rpc.provider;
/**
* 服务提供方 实现
*
* @author zab
* @date 2020-05-16 17:52
*/
public class ProviderServiceImpl implements ProviderService {
@Override
public String hello(String str) {
return str == null ? "hello consumer." : str + "---> hello consumer. I can do something else for you!";
}
}
服务“注册中心”
这里的注册中心是写死的,只是描述原理,功能很死板。
package rpc.server;
import java.io.File;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* RPC服务端,将provider的服务发布成远程服务,供消费者调用
*
* @author zab
* @date 2020-05-16 17:55
*/
public class RpcServer {
/**
* 自定义线程池,自定义线程工厂和拒绝策略
*/
static Executor executor = new ThreadPoolExecutor(10, 10, 0,
TimeUnit.SECONDS, new LinkedBlockingDeque<>(),
r -> {
Thread t = new Thread(r);
t.setName("myThread");
return t;
},
(r, executor) -> System.out.println(r.toString() + " is discard")
);
/**
* 处理RPC请求调用
*/
public static void startServer(String hostName, int port) throws Exception {
ServerSocket server = new ServerSocket();
server.bind(new InetSocketAddress(hostName, port));
try {
while (true) {
executor.execute(new RpcHandleTask(server.accept()));
}
} finally {
server.close();
}
}
/**
* 接收TCP数据,根据接口,反射调用接口实现类provider的方法
*/
private static class RpcHandleTask implements Runnable {
Socket socket = null;
public RpcHandleTask(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
ObjectInputStream input = null;
ObjectOutputStream output = null;
try {
input = new ObjectInputStream(socket.getInputStream());
//读出接口名
String interfaceName = input.readUTF();
//找到接口所在相对地址
String path = interfaceName.substring(0, interfaceName.lastIndexOf("."));
Class<?> clazz = Class.forName(interfaceName);
//找到接口所在文件下所有文件
File[] files = new File(clazz.getResource("/").getFile() + path.replaceAll("\\.", "\\/")).listFiles();
for (File file : files) {
//找文件,而不是文件夹
if (!file.isDirectory()) {
//path不一定是provider实现类的,这里需要实现类的path
String implPath = path + "." + file.getName().replaceAll(".class", "");
//获取实现类的class
Class<?> implClass = Class.forName(implPath);
//不是接口,并且实现类的接口名和传过来的接口名一致
if (!implClass.isInterface() && interfaceName.equals(implClass.getInterfaces()[0].getName())) {
//获取消费者传过来的接口方法
String methodName = input.readUTF();
//获取消费者传过来的接口参数类型
Class<?>[] parameterTypes = (Class<?>[]) input.readObject();
Method method = implClass.getMethod(methodName, parameterTypes);
Object[] arguments = (Object[]) input.readObject();
//移花接木,调用实现类的方法
Object result = method.invoke(implClass.newInstance(), arguments);
output = new ObjectOutputStream(socket.getOutputStream());
output.writeObject(result);
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (output != null) {
try {
output.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (input != null) {
try {
input.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
服务消费方
package rpc;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.Socket;
/**
* 服务消费者远程调用服务提供方,处理连接、反序列化之类工作
*
* @author zab
* @date 2020-05-16 16:50
*/
public class RpcConsumer {
public Object remoteCall(final Class clazz, final InetSocketAddress addr,Object[] args) throws Throwable{
Socket socket = null;
ObjectOutputStream output = null;
ObjectInputStream input = null;
try {
socket = new Socket();
socket.connect(addr);
output = new ObjectOutputStream(socket.getOutputStream());
output.writeUTF(clazz.getName());
Method[] methods = clazz.getMethods();
for (Method method : methods) {
output.writeUTF(method.getName());
output.writeObject(method.getParameterTypes());
output.writeObject(args);
}
input = new ObjectInputStream(socket.getInputStream());
return input.readObject();
} finally {
if (socket != null) socket.close();
if (output != null) output.close();
if (input != null) input.close();
}
}
}
测试
package rpc;
import rpc.provider.ProviderService;
import rpc.server.RpcServer;
import java.net.InetSocketAddress;
public class Test {
static {
startServer();
}
public static void main(String[] args) throws Throwable {
RpcConsumer consumer = new RpcConsumer();
Object[] objArgs = {"调用远程方法--"};
Object providerService = consumer.remoteCall(ProviderService.class, new InetSocketAddress("localhost", 8083), objArgs);
System.out.println(providerService.toString());
}
private static void startServer() {
new Thread(new Test()::runServer).start();
}
private void runServer() {
try {
//远程TCP服务打开,provide发布到远程
RpcServer.startServer("localhost", 8083);
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试结果
技术分析
1、测试类:通过静态代码块调用独立线程启动服务。可以发现,服务消费方,只需要知道服务提供方的接口和地址端口,即可工作。
2、服务消费方:通过socket连接服务注册中心,传入要调用哪个接口、什么参数、何种方法。最后就拿到socket返回的输入流
3、服务注册中心:通过自定义线程池来处理连接,注册中心作为一个socket服务端,根据客户端传过来的接口反射获取接口路径,找到同路径下的该接口实现类,根据接口方法名,调用实现类的方法,最后把结果写入流,通过socket回写给服务消费方。
4、服务提供方:很简单,就一个hello方法。
阅读本案例代码,需要你掌握哪些java基础知识?
1、如何自定义线程池
2、反射原理
3、网络编程
4、Java8新特性
偷个懒,代码拷起来太麻烦!github链接在此:
https://github.com/Jackson-zhanganbing/message-middleware-demo.git