内容概要 :
RMI 示例
在了解RMI之前,先来看一个例子:
// 服务接口 package com.fjn.java.rmi.quickstart.server; import java.rmi.Remote; import java.rmi.RemoteException; public interface Hello extends Remote{ public String sayHello(String str) throws RemoteException; } // 服务实现 package com.fjn.java.rmi.quickstart.server; import java.io.Serializable; import java.rmi.RemoteException; public class HelloImpl implements Hello, Serializable { protected HelloImpl() throws RemoteException { super(); } private static final long serialVersionUID = 3556503295294925414L; @Override public String sayHello(String str) { return "Hello, "+str; } } // Server端发布服务: package com.fjn.java.rmi.quickstart.server; import java.net.MalformedURLException; import java.rmi.AlreadyBoundException; import java.rmi.NotBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.UnicastRemoteObject; public class ServerTest { public static void main(String[] args) throws RemoteException, AlreadyBoundException, InterruptedException, MalformedURLException, NotBoundException { // 创建Registry Registry registry=LocateRegistry.createRegistry(9998); Hello stub=(Hello) UnicastRemoteObject.exportObject(new HelloImpl(),0); // 将ref绑定到registry中 registry.rebind("Hello", stub); Hello stub2=(Hello)registry.lookup("Hello"); System.out.println(stub2); Thread.sleep(1000*60*60); } }
执行结果:
Hello, zhang san Hello, Zhang san
上面的这个图表示了RMI的架构图,设计说明:Server端只需向一个登记处(Registry)为一个远程对象注册名字(唯一),然后就可以等待客户端来使用了。客户端要调用一个远程方法前,先去登记处(Registry)look up(根据name),找到相应的对象(找到的是对象的引用),然后在客户端进行方法调用。因为客户端拿到的是远程对象的引用,所以就像是直接调用一样。
在有些情况下,客户端会从服务端取一此Class的bytecode,服务端同样也会从客户端取一些类的bytecode。这样做的前提是,对方那里有Web Server。获取时使用都是URL协议:URL protocol (e.g., HTTP, FTP, file, etc.) 。
The Remote
interface serves to identify interfaces whose methods may be invoked from a non-local virtual machine. Any object that is a remote object must directly or indirectly implement this interface. Only those methods specified in a "remote interface", an interface that extends java.rmi.Remote
are available remotely.
在这个接口中,没有任何方法。这个接口只是一个标记作用,标记那些可以在非本地使用的接口。也就是说,如果这个接口可以被RMI客户端使用,就必须extends这个接口,或者Remote的子接口。
将实现了Remote接口的对象称为远程对象。RMI用于分布式程序,因此也将远程对象称为分布式对象。
1)必须继承Remote接口
2)在远程接口中声明的方法必须满足下列要求:
A:方法必须抛出java.rmi.RemoteException(IOException,Exception也可以,因为这两个异常是RemoteException的父类)。
B:远程方法的声明中:如果远程对象出现参数或者返回值中,那么得使用远程接口本身。
3)远程接口如果也继承了其他的非远程接口,只要远程方法满足2)中的要求即可。作为远程接口本身,要继承Remote接口,在这个远程接口中直接声明的方法要满足2)中的要求。从非远程接口中继承来的方法也得满足2)中的要求。
4)接口的实现,必须实现Serializable接口。
为了保证远程调用时,程序的正常进行,需要对每一个远程方法都抛出RemoteException,或者它的父类:IOException、Exception。
何时会抛出RemoteException?
1) 协议错误时
2) 通信失败时(Server拒绝请求,或者没有连接成功)
3) 参数或者接口在marshall、unmarshall过程中出现问题
在前面已经知道Registry是一个登记处。用于登记提供的远程服务的。
Registry类提供了一些存储、获取远程对象(在存储时,远程对象的引用会与一个name绑定,name是唯一的,所以获取时可以根据name来获取)。
提供了下列方法:
bind、unbind、rebind 用于将远程对象与指定的name进行绑定和解绑。
list:用于列出已绑定的远程对象的name。返回结果是数组。
另外有一点很重要,在Registry中存储的是stub对象。而正在的对象是在RMI Server上。
当客户端通过Naming或者Registry查找(loopup)到的都是对象的stub。然后利用Stub的序列化与反序列化功能进行Client与Server端通信。
LocateRegistry是用于定位Registry在网络上的位置的,知道Registry在网络上的位置,就能取得Registry对象,根据Registry对象就可以找到要使用的远程对象,找到远程对象就可以去调用远程方法了。
Naming类提供了一些存储和获取远程对象(存储在Registry中的)的引用的方法。
提供的方法有:
提供的方法中,都有一个name参数,是字符串类型的。它是有一个标准的格式(是一个URL,但是少了URL中的schema部分,schema是rmi)。格式为:
//host:port/name 或者 rmi://host:port/name
Host就是定位到的Registry的host,端口也是。
从提供的方法上来看,Naming就是一个工具类,它提供的方法都是静态的,但是方法确实和Registry中的方法是一样的。如此,目前就学习了两种获取、绑定远程对象的方法了。
也就是说Registry在客户端和服务端都是可用的。绑定用于服务端,获取用于客户端。
方式一: 使用Registry绑定或者获取一个远程对象的stub。 方式二: 直接使用Naming来绑定或者获取一个远程对象的stub。