JAVA从零手写实现一个简单RPC框架
- 菜鸟笔记
- 场景模拟假设
- 客户端(用户逻辑服务)
- 服务端(数据查询服务)
- 运行结果
菜鸟笔记
RPC-远程过程调用
主要用于解决分布式系统中,服务之间的调用问题。远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑
例如:在实际后台开发中,开发者A负责数据查询服务,开发者B负责用户逻辑服务,那么开B需要调用A的用户数据查询服务,所以A.B需要有一个共同约定协调好的接口,A实现该接口,B调用该接口
场景模拟假设
1.localclient包为一个独立项目,负责用户逻辑服务
2.server包也是独立一个项目,负责数据查询服务
3.公用相同一个类接口UserApi
客户端(用户逻辑服务)
这里需要对动态代理需要一点了解
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.先运行服务端主函数再运行客户端主函数
2.好了,一个简单的RPC框架完成了,这里主要是为了方便理解PRC框架,实际上的运用需要考虑各因素问题的,为了加深自己理解,大牛请忽略本小白~~当然也接受宝贵意见评论~
3.–本文参考了作者Java编程精选,自己理解总结笔记—