1、本章关注Java分布式技术,特别是用于两个Java虚拟机之间的远程方法调用RMI。
2、我们想要这样一种机制:客户端的程序员以常规方式进行方法调用,而无需关心在数据在网络上传输或者解析响应的问题(解决方法是在客户端上安装一个代理类,由他处理技术细节)。
3、类似的,服务器端也需要有这样的功能,让传输和业务逻辑分离,于是有了如下的结构:
客户端 <-调用本地方法、返回-> 代理 <-->代理 <-调用本地方法,返回-> 服务器
4、代理之间的通信,可以用RMI(Java远程过程调用)、COBRA等实现。
5、RMI是EJB所选用的通信协议,当然它只能运行于Java平台中。
6、COBRA、SOAP等则是完全独立于语言的。客户端、服务器端可以用C、C++、Java或其他任何支持的语言实现。但是一般都需要一个通用描述语言来屏蔽跨语言导致的不一致,如COBRA是IDL文件,SOAP是WSDL。
7、Web-Service完全构建于HTTP请求和XML之上,由于解析XML的效率问题,光环已经逐渐退去。。
8、远程代码调用:在一台机器(客户端)上的代码,需要调用另一台机器(服务器)上的某一个方法。
9、客户端使用的代理对象叫做stub(书上翻译成存根,真心别扭)。存根负责将客户所调用的代码参数打包成一组字节,这一过程称作参数编组。在RMI中,这一过程是用序列化实现的(序列化的包括远程对象标识符、被调用的方法描述、编组后的参数)。
10、在服务器端,负责:定位索要调用的远程对象(同一端口绑定的服务下,可以有多个远程对象),调用所需要的对象方法、返回值或者异常,将上述结果打包返回给客户端存根。
11、上述过程虽然很复杂,但对程序员来说是透明的。
12、首先来实现RMI的服务器端部分:
首先是实现一个接口,它必须实现了import java.rmi.Remote,注意每个方法必须都抛出RemoteException异常!
package server; import java.rmi.Remote; import java.rmi.RemoteException; public interface Compute extends Remote { public int add(int a, int b) throws RemoteException; public int sub(int a, int b) throws RemoteException; }
然后是实现上述服务器端接口的业务逻辑,它必须继承自UnicastRemoteObject,这是为了naming注册用的,实际上这两步是可以二合一的。
package server; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class ComputeImpl extends UnicastRemoteObject implements Compute { protected ComputeImpl() throws RemoteException { super(); } @Override public int add(int a, int b) throws RemoteException { return a + b; } @Override public int sub(int a, int b) throws RemoteException { return a - b; } }
如果我们不想extends自UnicastRemoteObject,则可以在对象中如下操作:
UnicastRemoteObject.exportObject(this, port);
13、在实现客户端的stub之前,我们还需要一个方法,让客户端可以定位到远程服务器的对象上,怎么搞呢?Java中最常用的就是注册名字方法。
RMI的URL以rmi://开头,后面跟着IP:Port/对象的名字,比如:
rmi://xx.xx.xx.xx:999/compute
下面的代码将服务器端的对象ComputeImpl注册到RMI的URL上。这部分代码其实才是真正的“服务器”绑定端口、IP那部分。
package server; import java.rmi.Naming; import java.rmi.registry.LocateRegistry; public class ComputeServer { public static void main(String[] args) throws Exception { // Start Reg server LocateRegistry.createRegistry(9999); // Make object ComputeImpl comp_impl = new ComputeImpl(); // Bind Naming.rebind("rmi://127.0.0.1:9999/Compute", comp_impl); System.out.println("Server Started"); } }
14、下面就是客户端的程序了,实际上,客户端可以Naming查找,直接使用上面我们声明的接口,这样就是“代理”了,这个思路确实非常好……
package client; import java.rmi.Naming; import server.Compute; public class ComputeClient { public static void main(String[] args) throws Exception { Compute comp = (Compute) Naming.lookup("rmi://127.0.0.1:9999/Compute"); //comp is stub object System.out.println(comp.add(1, 3)); } }
15、RMI传递对象:理论上和传输普通值是一样的,但实际上发送者和接收者看到的对象是不在同一片内存中的。但是,只要可序列化的(实现了Serializable)的都可以传输。
16、远程调用比本地调用要慢很多很多。
17、stub对象的equals和hashCode无意义,它们只表示了在远程的位置,只要指向的对象相同,就认为它们的equals和hashCode相同。
18、同理,stub对象也无法clone,会抛异常。
19、RMI也支持激活(activation),即让对象延迟加载,这时需要继承Activatable而不是UnicastRemoteObject。
20、WebService这种已经半过气的技术略过。
本章完毕。