本文以一个简单的例子介绍开发Remote Method Invocation/RMI应用程序的完整流程。
作为学习《编程导论(Java)·3.1.3 接口与实现分离》和《4.3 Java接口》的补充资料;
作为学习Android 远程服务的背景知识。
设想有这样一个情景:网络上存在一个基于Java的计算引擎,该服务器为我们/客户端执行某个(例如计算密集型的)Java程序。客户端能够调用某个巨型计算机/远程服务器上的远程对象的远程方法,而且如同调用本地对象的方法一样方便自然,这是一个引人注目的、令人开心的事情。
操作分布即把计算分散给不同主机进行处理、或者说 分布式对象计算[distributed object computing]是一个重要的编程领域,最典型而简单的实现方式就是 远程方法调用[Remote Method Invocation, RMI]。本节以一个简单的例子介绍开发RMI应用程序的完整流程。RMI应用程序将由3部分构成。
在各层面的网络编程中,会接触到各种协议(protocol)。网络协议(network protocol、简称协议)是数据格式的形式化描述和交换那些数据的规则集。大多数典型的应用都有了标准化的网络协议,如FTP、HTTP、Internet Protocol (互联网协议、IP)等等。
在面向对象程序中,协议或接口指客户(clinet)或客户程序所需要知道的所有信息,包括方法头(方法原型)、和非常重要的说明文档。
RMI应用程序遵循服务器-客户端模型。因此服务器-客户端之间的协议,最佳的封装方式是Java接口。
作为一个编程框架的RMI,需要为程序员尽可能地隐藏计算细节,如网络错误、程序间通信、分布式系统中的垃圾收集等等。Java RMI框架成功的隐藏网络通信的细节,极大的简化了分布式对象计算程序的开发。当然,本地对象与远程对象毕竟存在重大的差别,因而RMI不可能完全透明。随着分布式系统需求的增长,分布式系统还需要解决其它问题,如事务处理、安全性、一致性管理等等,建立在RMI基础上的其它框架不断被提出。
自定义的远程接口IHello是一个Java接口——封装服务器-客户端之间的协议;IHello基于RMI框架,是java.rmi.Remote的子类型。
在开发环境如NetBeans中创建项目RemoteInterface:package com.yqj2065; import java.rmi.Remote; import java.rmi.RemoteException; public interface IHello extends Remote{ /** * 业务方法的简化。简单的返回Hello Remote Method Invocation! * @return 返回Hello Remote Method Invocation! * @throws java.rmi.RemoteException */ public String hello() throws RemoteException; /** * 一个简单的业务方法,echo。 * @param str * @return echo: str * @throws java.rmi.RemoteException */ public String hello(String str) throws RemoteException; }
这里要强调:基础很重要。简单的例子并非无聊或一无是处。
1.java.rmi.Remote
java.rmi.Remote是所有远程接口的父类型。它是一个标记接口,其作用是标识它的(子接口中定义的)方法可以由其他Java虚拟机上的程序调用。
JDK中预定义了一些远程接口,如ActivationInstantiator,ActivationMonitor, ActivationSystem, Activator, DGC, Registry, RMIConnection,RMIServer,也定义了一些实现类,如UnicastRemoteObject。
IHello的每个方法,必须throwsjava.rmi.RemoteException或其父类。除了应用程序本身可能出现的异常之外,每个方法的远程调用都可能失败,例如遭遇服务器关机或服务器超载。从JDK 1.4 开始,已对该异常作出改进,这里暂时不讨论。
2. 生成jar文件
远程接口作为C/S双方之间的“协议”,会被双方程序员所使用。如果需要,应该将下列内容打包:
² 远程接口
² 远程方法抛出的自定义异常
² 中介性质的类,用于在C/S之间传输数据的小类,例如作为方法的参数或返回值的类型。
1. 实现远程服务接口
按照接口与实现相分离原则,客户端并不需要关心远程对象是谁、其实现(方法体)如何,因为客户端针对远程接口编程。而服务器端则需要实现远程接口。
实现类HelloImpl也可以命名为HelloServer,其对象称为远程对象(remote objects)或称为分布式对象(Distributed Object),是指其方法能够跨JVM调用的对象。如果
public classHelloImpl implements IHello
则需要显式导出 [export]对象,简单起见,将HelloImpl作为java.rmi.server.RemoteServer的子类如UnicastRemoteObject的子类。package rmiserver; import com.yqj2065.IHello; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class HelloImpl extends UnicastRemoteObject implements IHello { /** * 因为UnicastRemoteObject的构造方法抛出了RemoteException异常, * 因此必须定义构造器取代默认构造器, public HelloImpl() throws RemoteException { } @Override public String hello() throws RemoteException { return "Hello Remote Method Invocation!"; } @Override public String hello(String str) throws RemoteException { return "echo:" + str ; } }
2. 启动服务器
启动服务器,需要一个public static void main(String args[])方法。可以将main放在上面的HelloImpl类中,也可以单独使用一个类,取名RMIServer或RMIServerSetup。
事实上,服务器与客户端之间,还有一个重要问题要解决:客户端怎样找到远程服务?
java.rmi.Naming提供RMI 命名服务。package rmiserver; import com.yqj2065.IHello; import java.net.MalformedURLException; import java.nio.channels.AlreadyBoundException; import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; public class RMIServer { public static final int port =8000; public static final String registryName = "rmi://localhost:"+port+"/RHello"; public static void main(String[] args) throws java.rmi.AlreadyBoundException { try { IHello rhello = new HelloImpl(); LocateRegistry.createRegistry(port); Naming.bind(registryName,rhello); System.out.println(">>>>>RMIServer has been started and registered!"); } catch (RemoteException e) { System.out.println("RemoteException"); } catch (AlreadyBoundException e) { System.out.println("AlreadyBoundException"); } catch (MalformedURLException e) { System.out.println("MalformedURLException"); } } }
RMI的目的是使客户端对远程方法的调用,如同本地对象的方法调用一样,方便自然。
在开发环境如NetBeans中创建项目RMIClient时,为它的库添加依赖包。
注意:服务器和客户端代码中都使用的port和registryName,事实上属于服务器-客户端之间的约定,因而应该放在远程接口中。
请自行重构相关的代码。重构后的RMIClient如下所示。package rmiclient; import com.yqj2065.IHello; import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.RemoteException; public class RMIClient { //public static final int port =8000; //public static final String registryName = "rmi://localhost:"+port+"/RHello"; public static void main(String[] args) { try { IHello h =(IHello) Naming.lookup(IHello.registryName); System.out.println(h.hello()); System.out.println(h.hello("abcd")); } catch (NotBoundException | MalformedURLException | RemoteException e) { } } }
1.启动RMI服务器
在NetBeans中运行RMIServer项目,如图所示。2.运行客户端程序
客户程序调用h.hello()、hello("abcd")后自动断开与服务器的连接。