{"id":13932561,"url":"https://github.com/jayknoxqu/rmi-example","last_synced_at":"2025-07-19T16:31:41.902Z","repository":{"id":111919874,"uuid":"140606562","full_name":"jayknoxqu/rmi-example","owner":"jayknoxqu","description":"分布式通讯(rmi) remote method invocation example(java、scala、kotlin、springboot、zookeeper)","archived":false,"fork":false,"pushed_at":"2023-10-11T20:58:20.000Z","size":149,"stargazers_count":48,"open_issues_count":1,"forks_count":29,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-08-08T21:18:26.959Z","etag":null,"topics":["kotlin","rmi","scala","springboot2","zookeeper"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jayknoxqu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2018-07-11T17:12:31.000Z","updated_at":"2024-03-18T08:08:57.000Z","dependencies_parsed_at":"2023-10-21T11:56:54.477Z","dependency_job_id":null,"html_url":"https://github.com/jayknoxqu/rmi-example","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jayknoxqu%2Frmi-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jayknoxqu%2Frmi-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jayknoxqu%2Frmi-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jayknoxqu%2Frmi-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jayknoxqu","download_url":"https://codeload.github.com/jayknoxqu/rmi-example/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226643752,"owners_count":17662966,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["kotlin","rmi","scala","springboot2","zookeeper"],"created_at":"2024-08-07T21:01:07.843Z","updated_at":"2024-11-26T23:30:36.665Z","avatar_url":"https://github.com/jayknoxqu.png","language":"Kotlin","readme":"### RMI简介\n\n​      **Java RMI**，即 **远程方法调用**([Remote Method Invocation](https://en.wikipedia.org/wiki/Distributed_object_communication))，一种用于实现**远程过程调用**(RPC)[(Remote procedure call)](https://en.wikipedia.org/wiki/Remote_procedure_call)的Java API， 能直接传输序列化后的Java对象和[分布式垃圾收集](https://en.wikipedia.org/wiki/Distributed_Garbage_Collection)。它的实现依赖于[Java虚拟机](https://en.wikipedia.org/wiki/Java_Virtual_Machine)(JVM)，因此它仅支持从一个JVM到另一个JVM的调用。\n\n![rmi架构图](https://raw.githubusercontent.com/jayknoxqu/rmi-example/master/images/rmi_architecture.png)\n\n\n\n### rmi的实现\n\n#### (1) 直接使用Registry实现rmi\n\n##### 服务端:\n\n```java\npublic class RegistryService {\n    public static void main(String[] args) {\n        try {\n            // 本地主机上的远程对象注册表Registry的实例,默认端口1099\n            Registry registry = LocateRegistry.createRegistry(1099);\n            // 创建一个远程对象\n            HelloRegistryFacade hello = new HelloRegistryFacadeImpl();\n            // 把远程对象注册到RMI注册服务器上，并命名为HelloRegistry\n            registry.rebind(\"HelloRegistry\", hello);\n            System.out.println(\"======= 启动RMI服务成功! =======\");\n        } catch (RemoteException e) {\n            e.printStackTrace();\n        }\n    }\n}\n```\n\n\n##### 接口: \n\n继承Remote接口\n\n```\npublic interface HelloRegistryFacade extends Remote {\n\n    String helloWorld(String name) throws RemoteException;\n\n}\n```\n\n##### 接口实现: \n\n 继承UnicastRemoteObject\n\n```\npublic class HelloRegistryFacadeImpl extends UnicastRemoteObject implements HelloRegistryFacade{\n\n\tpublic HelloRegistryFacadeImpl() throws RemoteException {\n        super();\n    }\n\n    @Override\n    public String helloWorld(String name) {\n        return \"[Registry] 你好! \" + name;\n    }\n\n}\n```\n\n##### 客户端:\n\n```java\npublic class RegistryClient {\n    public static void main(String[] args) {\n        try {\n            Registry registry = LocateRegistry.getRegistry(1099);\n            HelloRegistryFacade hello = (HelloRegistryFacade) registry.lookup(\"HelloRegistry\");\n            String response = hello.helloWorld(\"ZhenJin\");\n            System.out.println(\"=======\u003e \" + response + \" \u003c=======\");\n        } catch (NotBoundException | RemoteException e) {\n            e.printStackTrace();\n        }\n    }\n}\n```\n\n##### 图解:\n\n出处:`https://www.tutorialspoint.com/java_rmi/java_rmi_introduction.htm`\n\n![rmi调用过程](https://raw.githubusercontent.com/jayknoxqu/rmi-example/master/images/registry.jpg)\n\n```\nRegistry(注册表)是放置所有服务器对象的命名空间。\n每次服务端创建一个对象时，它都会使用bind()或rebind()方法注册该对象。\n这些是使用称为绑定名称的唯一名称注册的。\n\n要调用远程对象，客户端需要该对象的引用,如(HelloRegistryFacade)。\n即通过服务端绑定的名称(HelloRegistry)从注册表中获取对象(lookup()方法)。\n```\n\n\n\n#### (2) 使用Naming方法实现rmi\n\n##### 服务端:\n\n```java\npublic class NamingService {\n    public static void main(String[] args) {\n        try {\n            // 本地主机上的远程对象注册表Registry的实例\n            LocateRegistry.createRegistry(1100);\n            // 创建一个远程对象\n            HelloNamingFacade hello = new HelloNamingFacadeImpl();\n            // 把远程对象注册到RMI注册服务器上，并命名为Hello \n            //绑定的URL标准格式为：rmi://host:port/name\n            Naming.bind(\"rmi://localhost:1100/HelloNaming\", hello);\n            System.out.println(\"======= 启动RMI服务成功! =======\");\n        } catch (RemoteException | MalformedURLException | AlreadyBoundException e) {\n            e.printStackTrace();\n        }\n    }\n}\n```\n\n接口和接口实现和Registry的方式一样\n\n\n\n##### 客户端:\n\n```java\npublic class NamingClient {\n    public static void main(String[] args) {\n        try {\n            String remoteAddr=\"rmi://localhost:1100/HelloNaming\";\n            HelloNamingFacade hello = (HelloNamingFacade) Naming.lookup(remoteAddr);\n            String response = hello.helloWorld(\"ZhenJin\");\n            System.out.println(\"=======\u003e \" + response + \" \u003c=======\");\n        } catch (NotBoundException | RemoteException | MalformedURLException e) {\n            e.printStackTrace();\n        }\n    }\n}\n```\n\n\n\n##### Naming部分源码:\n\n```\npublic static Remote lookup(String name)\n    throws NotBoundException,java.net.MalformedURLException,RemoteException{\n    ParsedNamingURL parsed = parseURL(name);\n    Registry registry = getRegistry(parsed);\n\n    if (parsed.name == null)\n        return registry;\n    return registry.lookup(parsed.name);\n}\n```\n\nNaming其实是对Registry的一个封装\n\n\n\n### Scala实现rmi\n\n上面说了rmi是通过JVM虚拟机进行一个远程调用的,我们通过Scala,kotlin等jvm语言印证下\n\n##### 服务端:\n\n```scala\nobject ScalaRmiService extends App {\n  try {\n    val user:UserScalaFacade = new UserScalaFacadeImpl\n    LocateRegistry.createRegistry(1103)\n    Naming.rebind(\"rmi://localhost:1103/UserScala\", user)\n    println(\"======= 启动RMI服务成功! =======\")\n  } catch {\n    case e: IOException =\u003e println(e)\n  }\n}\n```\n\n##### 接口\n\n```\ntrait UserScalaFacade extends Remote {\n\n  /**\n    * 通过用户名获取用户信息\n    */\n  @throws(classOf[RemoteException])\n  def getByName(userName: String): User\n\n  /**\n    * 通过用户性别获取用户信息\n    */\n  @throws(classOf[RemoteException])\n  def getBySex(userSex: String): List[User]\n\n}\n```\n\n##### 接口实现:\n\n```\nclass UserScalaFacadeImpl extends UnicastRemoteObject with UserScalaFacade {\n\n  /**\n    * 模拟一个数据库表\n    */\n  private lazy val userList = List(\n    new User(\"Jane\", \"女\", 16),\n    new User(\"jack\", \"男\", 17),\n    new User(\"ZhenJin\", \"男\", 18)\n  )\n\n  override def getByName(userName: String): User = userList.filter(u =\u003e userName.equals(u.userName)).head\n\n  override def getBySex(userSex: String): List[User] = userList.filter(u =\u003e userSex.equals(u.userSex))\n\n}\n```\n\n##### 实体类:\n\n实体类必须实现序列化(Serializable)才能进行一个远程传输\n\n```\nclass User(name: String, sex: String, age: Int) extends Serializable {\n\n  var userName: String = name\n  var userSex: String = sex\n  var userAge: Int = age\n  override def toString = s\"User(userName=$userName, userSex=$userSex, userAge=$userAge)\"\n\n}\n```\n\n##### Scala客户端:\n\n```\nobject ScalaRmiClient extends App {\n\n  try {\n\n    val remoteAddr=\"rmi://localhost:1103/UserScala\"\n    val userFacade = Naming.lookup(remoteAddr).asInstanceOf[UserScalaFacade]\n\n    println(userFacade.getByName(\"ZhenJin\"))\n    System.out.println(\"--------------------------------------\")\n    for (user \u003c- userFacade.getBySex(\"男\")) println(user)\n\n  } catch {\n    case e: NotBoundException =\u003e println(e)\n    case e: RemoteException =\u003e println(e)\n    case e: MalformedURLException =\u003e println(e)\n  }\n\n} \n```\n\n##### Java客户端:\n\n```\npublic class JavaRmiClient {\n\n    public static void main(String[] args) {\n\n        try {\n            String remoteAddr=\"rmi://localhost:1103/UserScala\";\n            UserScalaFacade userFacade = (UserScalaFacade) Naming.lookup();\n\n            User zhenJin = userFacade.getByName(\"ZhenJin\");\n            System.out.println(zhenJin);\n            System.out.println(\"--------------------------------------\");\n            List\u003cUser\u003e userList = userFacade.getBySex(\"男\");\n            System.out.println(userList);\n\n        } catch (NotBoundException | RemoteException | MalformedURLException e) {\n            e.printStackTrace();\n        }\n    }\n}\n```\n\n上面试验可以证明Scala和Java是可以互通的,Scala本身也是可以直接引用Java类的\n\n### 序列化简介\n\n​     **序列化**([Serialization](https://en.wikipedia.org/wiki/Serialization))是将**数据结构**或对象状态转换为可以存储（例如，在**文件**或存储器[缓冲区中](https://en.wikipedia.org/wiki/Data_buffer)）或传输（例如，通过**网络**连接）的格式的过程, **反序列化**(Deserialization)则是从一系列字节中提取数据结构的相反操作.\n\n![序列化与反序列化](https://raw.githubusercontent.com/jayknoxqu/rmi-example/master/images/Serialization-deserialization.jpg)\n\n### Kotlin实现rmi\n\n##### 服务端:\n\n```kotlin\nfun main(args: Array\u003cString\u003e) {\n    try {\n        val hello: HelloKotlinFacade = HelloKotlinFacadeImpl()\n        LocateRegistry.createRegistry(1102)\n        Naming.rebind(\"rmi://localhost:1101/HelloKotlin\", hello)\n        println(\"======= 启动RMI服务成功! =======\")\n    } catch (e: IOException) {\n        e.printStackTrace()\n    }\n}\n```\n\n##### 客户端:\n\n```kotlin\nfun main(args: Array\u003cString\u003e) {\n    try {\n        val hello = Naming.lookup(\"rmi://localhost:1102/HelloKotlin\") as HelloKotlinFacade\n        val response = hello.helloWorld(\"ZhenJin\")\n        println(\"=======\u003e $response \u003c=======\")\n    } catch (e: NotBoundException) {\n        e.printStackTrace()\n    } catch (e: RemoteException) {\n        e.printStackTrace()\n    } catch (e: MalformedURLException) {\n        e.printStackTrace()\n    }\n}\n```\n\n实现和接口省略...\n\n### SpringBoot实现rmi\n\nStringBoot通过配置就可以简单实现rmi了\n\n##### 服务端:\n\n```java\n@Configuration\npublic class RmiServiceConfig {\n    @Bean\n    public RmiServiceExporter registerService(UserFacade userFacade) {\n        RmiServiceExporter rmiServiceExporter = new RmiServiceExporter();\n        rmiServiceExporter.setServiceName(\"UserInfo\");\n        rmiServiceExporter.setService(userFacade);\n        rmiServiceExporter.setServiceInterface(UserFacade.class);\n        rmiServiceExporter.setRegistryPort(1101);\n        return rmiServiceExporter;\n    }\n}\n```\n\n\n\n##### 客户端:\n\n```java\n@Configuration\npublic class RmiClientConfig {\n\n    @Bean\n    public UserFacade userInfo() {\n        RmiProxyFactoryBean rmiProxyFactoryBean = new RmiProxyFactoryBean();\n        rmiProxyFactoryBean.setServiceUrl(\"rmi://localhost:1101/UserInfo\");\n        rmiProxyFactoryBean.setServiceInterface(UserFacade.class);\n        rmiProxyFactoryBean.afterPropertiesSet();\n        return (UserFacade) rmiProxyFactoryBean.getObject();\n    }\n\n}\n```\n\n##### 客户端测试类:\n\n```\n@Autowired\nprivate UserFacade userFacade;\n    \n@Test\npublic void userBySexTest() {\n    try {\n        List\u003cUser\u003e userList = userFacade.getBySex(\"男\");\n        userList.forEach(System.out::println);\n    } catch (RemoteException e) {\n        e.printStackTrace();\n    }\n}\n```\n\n通过测试类可以看出,这和我们平时的程序调用内部方法没什么区别!\n\n\n\n### rmi调用过程\n\n大家可以通过下面文章加深了解:\n\nhttps://stuff.mit.edu/afs/athena/software/java/java_v1.2.2/distrib/sun4x_56/docs/guide/rmi/Factory.html\n\n![rmi工厂调用的过程](https://raw.githubusercontent.com/jayknoxqu/rmi-example/master/images/RMI-Factory.png)\n\n- 有两个远程服务接口可供client调用，Factory和Product接口\n\n- FactoryImpl类实现了Factory接口，ProductImpl类实现了Product接口\n\n  ```\n  1. FactoryImpl被注册到了rmi-registry中\n  2. client端请求一个Factory的引用 \n  3. rmi-registry返回client端一个FactoryImpl的引用 \n  4. client端调用FactoryImpl的远程方法请求一个ProductImpl的远程引用\n  5. FactoryImpl返回给client端一个ProductImpl引用 \n  6. client通过ProductImpl引用调用远程方法 \n  ```\n\n  \n\n### Zookeeper实现rmi\n\n#### 安装Zookeeper\n\n解压 ZooKeeper\n\n```\ntar -zxvf zookeeper-3.4.12.tar.gz\n```\n\n在 conf 目录新建 zoo.cfg\n\n \n\n```\ncd zookeeper-3.4.12/conf\nvim zoo.cfg\n```\n\n\n\nzoo.cfg 代码如下（自己指定 log 文件目录）：\n\n```\ntickTime=2000\ndataDir=/usr/local/zookeeper-3.4.12/data \ndataLogDir=/usr/local/zookeeper-3.4.12/log\nclientPort=2181\n```\n\n\n\n在 bin 目录下，启动 Zookeeper：\n\n```\ncd zookeeper-3.4.12/bin\n./zkServer.sh start\n```\n\n\n\n\n#### 消费者:\n出处:`http://www.importnew.com/20344.html`\n```\npublic class RmiConsumer {\n\n    // 用于等待 SyncConnected 事件触发后继续执行当前线程\n    private CountDownLatch latch = new CountDownLatch(1);\n\n    // 定义一个 volatile 成员变量，用于保存最新的 RMI 地址（考虑到该变量或许会被其它线程所修改，一旦修改后，该变量的值会影响到所有线程）\n    private volatile List\u003cString\u003e urlList = new ArrayList\u003c\u003e();\n\n    // 构造器\n    public RmiConsumer() {\n        ZooKeeper zk = connectServer(); // 连接 ZooKeeper 服务器并获取 ZooKeeper 对象\n        if (zk != null) {\n            watchNode(zk); // 观察 /registry 节点的所有子节点并更新 urlList 成员变量\n        }\n    }\n\n    // 查找 RMI 服务\n    public \u003cT extends Remote\u003e T lookup() {\n        T service = null;\n        int size = urlList.size();\n        if (size \u003e 0) {\n            String url;\n            if (size == 1) {\n                url = urlList.get(0); // 若 urlList 中只有一个元素，则直接获取该元素\n                log.debug(\"using only url: {}\", url);\n            } else {\n                url = urlList.get(ThreadLocalRandom.current().nextInt(size)); // 若 urlList 中存在多个元素，则随机获取一个元素\n                log.debug(\"using random url: {}\", url);\n            }\n            service = lookupService(url); // 从 JNDI 中查找 RMI 服务\n        }\n        return service;\n    }\n\n    // 连接 ZooKeeper 服务器\n    private ZooKeeper connectServer() {\n        ZooKeeper zk = null;\n        try {\n            zk = new ZooKeeper(Constant.ZK_CONNECTION_STRING, Constant.ZK_SESSION_TIMEOUT, new Watcher() {\n                @Override\n                public void process(WatchedEvent event) {\n                    if (event.getState() == Event.KeeperState.SyncConnected) {\n                        latch.countDown(); // 唤醒当前正在执行的线程\n                    }\n                }\n            });\n            latch.await(); // 使当前线程处于等待状态\n        } catch (IOException | InterruptedException e) {\n            log.error(\"\", e);\n        }\n        return zk;\n    }\n\n    // 观察 /registry 节点下所有子节点是否有变化\n    private void watchNode(final ZooKeeper zk) {\n        try {\n            List\u003cString\u003e nodeList = zk.getChildren(Constant.ZK_REGISTRY_PATH, event -\u003e {\n                if (event.getType() == Watcher.Event.EventType.NodeChildrenChanged) {\n                    watchNode(zk); // 若子节点有变化，则重新调用该方法（为了获取最新子节点中的数据）\n                }\n            });\n            List\u003cString\u003e dataList = new ArrayList\u003c\u003e(); // 用于存放 /registry 所有子节点中的数据\n            for (String node : nodeList) {\n                byte[] data = zk.getData(Constant.ZK_REGISTRY_PATH + \"/\" + node, false, null); // 获取 /registry 的子节点中的数据\n                dataList.add(new String(data));\n            }\n            log.debug(\"node data: {}\", dataList);\n            urlList = dataList; // 更新最新的 RMI 地址\n        } catch (KeeperException | InterruptedException e) {\n            log.error(\"\", e);\n        }\n    }\n\n    // 在 JNDI 中查找 RMI 远程服务对象\n    @SuppressWarnings(\"unchecked\")\n    private \u003cT\u003e T lookupService(String url) {\n        T remote = null;\n        try {\n            remote = (T) Naming.lookup(url);\n        } catch (NotBoundException | MalformedURLException | RemoteException e) {\n            log.error(\"远程查找出错!\", e);\n        }\n        return remote;\n    }\n}\n```\n\n#### 生产者:\n\n```\npublic class RmiProvider {\n\n\n    /**\n     * 用于等待 SyncConnected 事件触发后继续执行当前线程\n     */\n    private CountDownLatch latch = new CountDownLatch(1);\n\n    // 发布 RMI 服务并注册 RMI 地址到 ZooKeeper 中\n    public void publish(Remote remote, String host, int port) {\n        String url = publishService(remote, host, port); // 发布 RMI 服务并返回 RMI 地址\n        if (url != null) {\n            ZooKeeper zk = connectServer(); // 连接 ZooKeeper 服务器并获取 ZooKeeper 对象\n            if (zk != null) {\n                createNode(zk, url); // 创建 ZNode 并将 RMI 地址放入 ZNode 上\n            }\n        }\n    }\n\n     /**\n      *发布 RMI 服务\n      */\n    private String publishService(Remote remote, String host, int port) {\n        String url = null;\n        try {\n            url = String.format(\"rmi://%s:%d/%s\", host, port, remote.getClass().getName());\n            LocateRegistry.createRegistry(port);\n            Naming.rebind(url, remote);\n            log.debug(\"publish rmi service (url: {})\", url);\n        } catch (RemoteException | MalformedURLException e) {\n            log.error(\"\", e);\n        }\n        return url;\n    }\n\n    // 连接 ZooKeeper 服务器\n    private ZooKeeper connectServer() {\n        ZooKeeper zk = null;\n        try {\n            zk = new ZooKeeper(Constant.ZK_CONNECTION_STRING, Constant.ZK_SESSION_TIMEOUT, new Watcher() {\n                @Override\n                public void process(WatchedEvent event) {\n                    if (event.getState() == Event.KeeperState.SyncConnected) {\n                        latch.countDown(); // 唤醒当前正在执行的线程\n                    }\n                }\n            });\n            latch.await(); // 使当前线程处于等待状态\n        } catch (IOException | InterruptedException e) {\n            log.error(\"\", e);\n        }\n        return zk;\n    }\n\n    /**\n     * 创建节点\n     */\n    private void createNode(ZooKeeper zk, String url) {\n        try {\n            byte[] data = url.getBytes();\n            String path = zk.create(Constant.ZK_PROVIDER_PATH, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);    // 创建一个临时性且有序的 ZNode\n            log.debug(\"create zookeeper node ({} =\u003e {})\", path, url);\n        } catch (KeeperException | InterruptedException e) {\n            log.error(\"\", e);\n        }\n    }\n}\n```\n\n\n\n#### 图解:\n\n![zookeeper](https://raw.githubusercontent.com/jayknoxqu/rmi-example/master/images/Zookeeper.jpg)\n\n\n\n\n\n代码已上传到GitHub上:https://github.com/jayknoxqu/rmi-example\n","funding_links":[],"categories":["Kotlin"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjayknoxqu%2Frmi-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjayknoxqu%2Frmi-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjayknoxqu%2Frmi-example/lists"}