1、 RMI(Remote Method Invocation)概念
RMI是远程方法调用的简称,象其名称暗示的那样,它能够帮助我们查找并执行远程对象的方法。通俗地说,远程调用就象将一个class放在A机器上,然后在B机器中调用这个class的方法。
让某个java虚拟机上的对象调用另一个java虚拟机中的对象和方法。
2、RMI术语
在研究代码之前,我们来看看必须编写哪些代码:
·远程对象:Remote 接口用于标识其方法可以从非本地虚拟机上调用的接口。任何远程对象都必须直接或间接实现此接口。只有在“远程接口”(扩展 java.rmi.Remote 的接口)中指定的这些方法才可远程使用。
·远程对象实现:这是一个实现远程对象的类。如果实现了远程对象,就能够覆盖该对象中的所有方法,因此,远程对象的实现类将真正包含我们希望导出的方法的代码。
·远程服务器:这是一个作为服务器使用的类,它是相对于要访问远程方法的客户端而言的。它存储着绑定的字符串和对象。
·远程客户端:这是一个帮助我们访问远程方法提供帮助的类,它也是最终用户。我们将使用查找和调用远程方法的方法在该类中调用远程方法。
Stub(存根)和Skeleton(骨架)
Stub和Skeleton是经过rmic命令生成的,我们的程序要通过远程调用,底层一定是套接字的字节传输,要一个对象序列化成为一个字节数组,传输到服务器或者客户端的对端之后,再把该对象反序列化成为对应的对象,这些网络传输的过程要求安全,稳定等等非常麻烦的操作,Stub驻留客户端,承担着代理远程对象的实现者的角色,Skeleton类帮助远程对象与Stub再RMI连接上进行通信。RMI的客户与Stub进行交换,Stub与Skeleton通信,Skeketon负责与服务器进行交互,因此有了Stub和Skeleton之后我们就不需要实现底层通信的细节,我们进行的远程调用,只需要通过接口对方法进行操作即可,使分布式调用实现了位置上的透明,即:远程调用就像本地调用一样。
下面这个例子是在网上搜的,觉得挺好的,这里就摘录 了
1. 创建远程接口及声明远程方法(ICal.java)
/**
* 如果想要用 远程对象调用 接口必须要继承Remote或者类必须要实现Remote接口
*/
public interface ICal extends Remote {
//接口定义的方法需要抛出RemoteException异常
public int sum(int add1, int add2) throws RemoteException;
}
2. 实现远程接口及远程方法(继承UnicastRemoteObject)(ICalImpl.java)
UnicastRemoteObject 类用于导出带 JRMP 的远程对象和获得与该远程对象通信的 stub,即使远程对象具有相应的存根,并使它能够远程客户的方法调用请求。该类实现了Serializable, Remote接口,可以实现底层的序列化对象网络传输的需求。
public class ICalImpl extends UnicastRemoteObject implements ICal {
protected ICalImpl() throws RemoteException {
super();
}
private static final long serialVersionUID = 1L;
@Override
public int sum(int add1, int add2) throws RemoteException {
System.out.println("server : add1=" + add1 + ",add2=" + add2);
return add1 + add2;
}
}
public interface IHello extends Remote {
public String sayHello(String name) throws RemoteException;
}
public class IHelloImpl extends UnicastRemoteObject implements IHello {
private static final long serialVersionUID = 1L;
protected IHelloImpl() throws RemoteException {
super();
}
@Override
public String sayHello(String name) throws RemoteException {
System.out.println("server : " + name + " come here ");
return "hello " + name;
}
}
3. 启动RMI注册服务,并注册远程对象(Server.java)
public class Server {
public static void main(String[] args) {
//声明我要暴露的对象
try {
ICal cal = new ICalImpl();
IHello hello = new IHelloImpl();
//本地主机上的远程对象注册表Registry的实例,并指定端口为 1100,
//这一步必不可少(Java默认端口是1099),必不可缺的一步,缺少注册表创建,则无法绑定对象到远程注册表上
LocateRegistry.createRegistry(1100);
Naming.bind("rmi://127.0.0.1:1100/IHello",hello);
Naming.bind("rmi://127.0.0.1:1100/ICal",cal);
System.out.println("Server already be started up ");
} catch (RemoteException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (AlreadyBoundException e) {
e.printStackTrace();
}
}
}
4. 客户端查找远程对象,并调用远程方法(RmiClient.java)
客户端只需要保有业务接口,不应该保有业务类的实现类。这样做的好处是一端服务器端业务实现发生了改变,而客户端可以通过RMI来获得改变的具体业务对象,所以这样我们可以在客户端执行大量的操作,可以只将持久化操作交由服务器。
public class RmiClient {
public static void main(String[] args) {
try {
ICal cal = (ICal)Naming.lookup("rmi://127.0.0.1:1100/ICal");
IHello hello = (IHello)Naming.lookup("rmi://127.0.0.1:1100/IHello");
System.out.println(cal.sum(2, 3));
System.out.println(hello.sayHello("kaobian"));
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
} catch (NotBoundException e) {
e.printStackTrace();
}
}
}
5. 执行程序:启动服务Server;运行客户端RmiClient进行调用
运行结果:
1、启动Server: Server already be started up
2、启动RmiClient :
Server :
server : add1=2,add2=3
server : kaobian come here
Client:
5
hello kaobian
备注:RMI是线程同步的,即获取远程对象,执行其方法,server端如果一直没有返回值,则客户端一直等待返回的结果,可以使用debug模式测试一下。
lookup("Hello")默认为从本机 127.0.0.1的1099端口上查找Hello命令对象
lookup("192.168.1.105/Hello")与原语句是同等的,因为默认端口号就是1099。
HelloInterface hello = (HelloInterface)Naming.lookup("//192.168.1.105:1099/Hello");