自己写了个Java|自己写了个Java RMI(远程方法调用)的实现案例

自己简单写了个Java RMI(远程方法调用)的实现案例。
为了更好理解RMI(远程方法调用)、序列化的意义等等,花费三天多的时间肝了一个Java RMI的实现案例。
!!!高能预警!!! 代码量有点大,先附上了简图用于理解
自己写了个Java|自己写了个Java RMI(远程方法调用)的实现案例
文章图片

整个过程分为两大步

  • 第一步--注册过程:客户端通过指定路由获取注册中心指定的远程客户端对象;
  • 第二部--服务调用过程:客户端通过远程客户端对象访问远程服务端(代理服务)从而访问到真实服务的实现
调整为舒适的姿势,慢慢看…… 废话少说,上代码!!!
1.定义远程标记接口
面向接口编程,具体作用看后面的代码怎么使用
// 标记接口:直接或间接实现MyRMI接口将获得远程调用的能力 public interface MyRMI{ }

2.编写RMI 服务注册中心
注册中心类:用于注册服务和获取服务,核心是hashMap路由表对象
/** * 注册中心:维护服务发布的注册表 */ public class MyRMIRegistry { // 默认端口 public final int REGISTRY_PORT = 10099; private String host; private int port; private Map bindings; public MyRMIRegistry(int port){ this.port = port; } public MyRMIRegistry(String host, int port){ this.host=host; this.port=port; }public void createRegistry(String serverName,MyRMI myRMI){ // 注册服务,并开启服务 this.bindings = new HashMap<>(); String host = null; try { host = Inet4Address.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { e.printStackTrace(); } // 路由规则可自行定义,只要能确保Key唯一即可 String binding = "myrmi://"+host+":"+port+"/"+serverName; this.bindings.put("myrmi://"+host+":"+port+"/"+serverName,myRMI); System.out.println("注册的服务有:"+bindings.keySet().toString()); MyRMIRegistryServer myRMIRegistryServer = new MyRMIRegistryServer(this.port, this.bindings); Executors.newCachedThreadPool().submit(myRMIRegistryServer); // 线程池启动服务}public MyRMI getRegistry(String serverName){ Socket socket = null; ObjectOutputStream out = null; ObjectInputStream in = null; MyRMI myRMI = null; // 通过 try { socket = new Socket(host, port); out = new ObjectOutputStream(socket.getOutputStream()); out.writeObject("myrmi://"+host+":"+port+"/"+serverName); in = new ObjectInputStream(socket.getInputStream()); myRMI = (MyRMI)in.readObject(); } catch (IOException e) { e.printStackTrace(); }catch (ClassNotFoundException e) { e.printStackTrace(); } return myRMI; } }

RMI 注册中心获取服务的线程:启动注册中心服务,等待客户端来获取路由表中的远程客户端
/** * RMI注册中心获取服务线程 */ public class MyRMIRegistryServer implements Runnable { private int port; private Map bindings; public MyRMIRegistryServer(Integer port,Map bindings){ this.port = port; this.bindings = bindings; }@Override public void run() { ServerSocket serverSocket = null; ObjectOutputStream out = null; ObjectInputStream in = null; try { serverSocket = new ServerSocket(this.port); while(true){ Socket socket = serverSocket.accept(); in = new ObjectInputStream(socket.getInputStream()); out = new ObjectOutputStream(socket.getOutputStream()); // 看看客户端想要什么服务 String serverName = (String)in.readObject(); Iterator iterator = bindings.keySet().iterator(); while (iterator.hasNext()){ String key = (String) iterator.next(); if(serverName.equals(key)){ // 给客户端响应服务对象 MyRMI myRMI = bindings.get(key); out.writeObject(myRMI); } } } } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); }finally { // 异常后进入 try { if (out!=null)out.close(); if (in!=null)in.close(); if (serverSocket!=null) serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } }}}

3.定义要发布的服务接口
需要提供RMI服务的接口,必须继承自定义的MyRMI标记接口
/** * 服务接口 */ public interface Hello extends MyRMI { public String sayHello(String name); }

4.服务用到的实体类
/** * 对象数据类:Person */ public class Person implements Serializable { // 序列化版本UID private static final long serialVersionUID = 1L; private String name; private int age; private String sex; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } @Override public String toString() { return "Person{" + "name='" + name + ", age=" + age + ", sex='" + sex + '}'; } public Person() { } public Person(String name, Integer age, String sex) { this.name = name; this.age = age; this.sex = sex; } }

5.实现要发布的服务接口
/** * 对外提供的服务实现 */ public class HelloImpl implements Hello { private static File file = new File("D:/HelloRMI.txt"); private static List list = new ArrayList<>(); @Override public String sayHello(String name) { String result = "没有获取到"+name+"的信息"; try { List personList = readList(); for(Person person:personList){ if (person.getName().equals(name)){ result = "Hello , welcome to the RMI! " + "姓名:"+name + " 年龄:"+person.getAge()+" 性别:"+person.getSex(); } } } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); }return result; }/** * 生成数据,为测试做准备 * @param args * @throws IOException * @throws ClassNotFoundException */ public static void main(String[] args) throws IOException, ClassNotFoundException { //数据准备:集合类都实现了序列化接口Serializable list.add(new Person("张三", 38, "男")); list.add(new Person("李四", 38, "男")); list.add(new Person("如花", 18, "女")); // 持久化对象数据 writerList(list); // 查询持久化对象数据 List personList = readList(); System.out.println("遍历持久化对象数据>"); for (Person person : personList) { System.out.println(person); if (person.getAge() == 38) { person.setAge(18); } }}public static void writerList(List list) throws IOException { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(file)); objectOutputStream.writeObject(list); objectOutputStream.close(); }public static List readList() throws IOException, ClassNotFoundException { // 读取普通文件反序列化 ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file)); List personList = (List) objectInputStream.readObject(); objectInputStream.close(); return personList; } }

6.远程客户端的线程类
用于自动生成服务接口(继承了MyRMI标记接口)的远程客户端类:这个类原本是通用类实现,为了方便实现,就直接实现Hello接口了
/** * 远程客户端的线程类的生成: *为了方便实现,这边直接实现服务接口编写 */ public class HelloClientThread implements Hello,Serializable { // 序列化版本UID private static final long serialVersionUID = 1L; private Map map = new HashMap<>(); // 报文对象:方法名和参数对象 private String ip; private int port; public HelloClientThread(String ip, int port){ this.ip = ip; this.port = port; }@Override public String sayHello(String name) { map.put("sayHello",name); String result = (String)send(); return result; }private Object send(){ Object o =null; Socket socket = null; ObjectOutputStream out = null; ObjectInputStream in = null; try { socket = new Socket(ip, port); out = new ObjectOutputStream(socket.getOutputStream()); in = new ObjectInputStream(socket.getInputStream()); // 告诉服务端我要调用什么服务 out.writeObject(map); // 获取服务实现对象 o = in.readObject(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); }finally { try { if (out!=null)out.close(); if (in!=null)in.close(); if (socket!=null) socket.close(); } catch (IOException e) { e.printStackTrace(); } } return o; } }

7.远程服务端的线程类
用于自动生成服务接口(继承了MyRMI标记接口)的远程服务端类:这个类原本也是通用类实现,为了方便实现,部分代码尚未做到解耦通用
/** * 远程服务端的线程类的生成: *为了方便实现,这边直接实现服务线程类 */ public class HelloServerThread implements Runnable { private Integer port; private MyRMI myRMI; public HelloServerThread(Integer port, MyRMI myRMI){ this.port = port; this.myRMI = myRMI; }@Override public void run() { ServerSocket serverSocket = null; ObjectOutputStream out = null; ObjectInputStream in = null; try { serverSocket = new ServerSocket(this.port); while(true){ Socket socket = serverSocket.accept(); in = new ObjectInputStream(socket.getInputStream()); out = new ObjectOutputStream(socket.getOutputStream()); // 看看客户端想要什么服务 Map map = (Map)in.readObject(); Iterator iterator = map.keySet().iterator(); while (iterator.hasNext()){ String key = (String) iterator.next(); if("sayHello".equals(key)){ // 给客户端响应服务对象 Hello hello = (Hello)myRMI; String result = hello.sayHello((String) map.get(key)); out.writeObject(result); } } } } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); }finally { // 异常后进入 try { if (out!=null)out.close(); if (in!=null)in.close(); if (serverSocket!=null) serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } }}}

8.远程客户端生成和远程服务端生成和启动的类
/** * 远程客户端生成和远程服务端生成和启动的类 */ public class RemoteSocketObject{ // 默认端口 private int port=18999; // 指定远程通讯端口和代理服务 public MyRMI createRemoteClient(MyRMI myRMI,int port){ if (port > 0) this.port=port; MyRMI myRMIClient = null; try { // 生成底层通讯服务端,并启动 HelloServerThread helloServerThread = new HelloServerThread(this.port, myRMI); Executors.newCachedThreadPool().submit(helloServerThread); // 线程池启动服务 // 生成底层通讯客户端 String localHost = Inet4Address.getLocalHost().getHostAddress(); System.out.println("host="+localHost+",port="+this.port); myRMIClient= new HelloClientThread(localHost, this.port); } catch (Exception e) { e.printStackTrace(); } return myRMIClient; }}

9.服务发布类
/** * RMI 服务发布类 */ public class HelloServer { public static void main(String[] args) {System.out.println("Create Hello Remote Method Invocation..."); // 实例化一个Hello Hello hello = new HelloImpl(); // 转换成远程服务,并提供远程客户端 Hello remoteClient = (Hello)new RemoteSocketObject().createRemoteClient(hello, 0); // 将服务实现托管到Socket服务 MyRMIRegistry myRMIRegistry = new MyRMIRegistry(16000); // 开启线程服务 myRMIRegistry.createRegistry("Hello",remoteClient); } }

10.客户端测试类
/** * 客户端测试类 *客户端只知道服务接口、服务发布的地址和服务发布的名称 */ public class TestHello { public static void main(String[] args) { // 注意不是127.0.0.1,不知道host的看server端启动后打印的信息 // 端口16000是注册中心的端口,底层代理服务的端口客户端无需知道 MyRMIRegistry client = new MyRMIRegistry("192.168.233.1", 16000); Hello hello = (Hello) client.getRegistry("Hello"); System.out.println(hello.sayHello("张三")); } }

11.总结
所有代码整下来,在真正的场景中:
客户端只知道:TestHello类、Hello接口定义、MyRMI标记接口、MyRMIRegistry注册类代码(路由表中只知道Key,不知道具体值);
服务端只知道:Hello接口、HelloImpl服务实现类、MyRMI标记接口、MyRMIRegistry注册类代码(路由表中知道Key和具体值);
关于其他的代码实现都是无感的,为了简单实现远程客户端和远程服务端,将服务接口耦合到两者上了,未做到解耦通用。
自己写了个Java|自己写了个Java RMI(远程方法调用)的实现案例
文章图片

Java往期文章
Java全栈学习路线、学习资源和面试题一条龙
我心里优秀架构师是怎样的?
免费下载经典编程书籍
更多优质文章和资源
自己写了个Java|自己写了个Java RMI(远程方法调用)的实现案例
文章图片

【自己写了个Java|自己写了个Java RMI(远程方法调用)的实现案例】原创不易、三联支持:分享,点赞,在看

    推荐阅读