Java RMI
当在一台计算机上的某个对象需要调用在另一台计算机上的某个对象时,它会发送一个包含这个请求的详细信息的网络消息。这个远程对象可能通过访问数据库也可能通过与其他对象通信来计算出响应。一旦该远程对象得到了客户端请求的东西,就将它送回客户端。
1. 客户与服务器的角色
所有分布式编程技术的基本思想都很简单:客户计算机产生一个请求,然后将这个请求通过网络发送到服务器。服务器处理这个请求,并发送一个针对该客户端的响应,供客户端进行分析。
我们必须要声明:这些请求和响应并不是在Web应用程序中看到的请求和响应。这里的客户端并非是Web浏览器,它可以是执行具有任意复杂度的业务规则的任何应用程序。客户端应用程序可以与用户之间进行交互,但也可以没有这种交互。如果有这种交互,它可以拥有一个命令行或Swing用户界面。用于请求和响应数据的协议允许传递任意对象,而传统的Web应用程序只限于对请求使用HTTP协议,对响应使用HTML。
我们真正想要的是这样一种机制,客户端程序员以常规的方式进行方法调用,而无需关心数据在网络上传输或者解析响应之类的问题。解决的办法是,在客户端为远程对象安装一个代理(proxy)。代理是位于客户端虚拟机中的一个对象,它对于客户端程序来说,看起来就像是要访问的远程对象一样。客户调用此代理,进行常规的方法调用。而客户端代理负责与服务器进行联系。
同样的,实现服务的程序员也不希望因与客户端之间的通信被绊住。解决方法是在服务器端安装第二个代理对象。该服务器代理与客户端代理进行通信,并且它将以常规方式调用服务器对象上的方法。
2. 远程方法调用
分布式计算的关键是远程方法调用。在一台机器(称为客户端)上的某些代码希望调用在另一台机器(远程对象)上的某个对象的一个方法。要实现这一点,方法的参数必须以某种方式传递到另一台机器上,而服务器必须得到通知,去定位远程对象并执行要调用的方法,并且必须将返回值传递回去。
存根与参数编组
当客户代码要在远程对象上调用一个远程方法时,实际上调用的是代理对象上的一个普通的方法,我们称此代理对象为存根(Stub)。
存根位于客户端机器上,而非服务器上。它知道如何通过网络与服务器联系。存根将远程方法所需的参数打包成一组字节。对参数编码的过程称作参数编组,参数编组的目的是将参数转换成适合在虚拟机之间进行传递的格式。在RMI协议中,对象是使用序列化机制进行编码的。
总的来说,客户端的存根方法构造了一个信息块,它由以下几部分组成:
Ø 被使用的远程对象的标识符;
Ø 被调用的方法的描述;
Ø 编组后的参数。
然后,存根将此信息发送给服务器。在服务器一端,接收对象执行以下动作:
Ø 定位要调用的远程对象;
Ø 调用所需的方法,并传递客户端提供的参数;
Ø 捕获返回值或该调用产生的异常;
Ø 将返回值编组,打包送回给客户端存根。
客户端存根对来自服务器端的返回值或异常进行反编组,就成为了调用存根的返回值。如果远程方法抛出了一个异常,那么存根就在客户端发起调用的处理空间中重新抛出该异常。
3. 配置远程方法调用
从一个简单的实例入手。远程对象表示的是一个仓库,而客户端程序向仓库询问某个产品的价格。
3.1 接口与实现
远程对象的能力是由在客户端和服务器之间共享的接口所表示的。下面的接口程序描述了远程仓库对象所提供的服务。
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Warehouse extends Remote {
publicdouble getPrice(String description) throwsRemoteException;
}
远程对象的接口必须扩展Remote接口,它位于java.rmi包中,接口中的所有方法还必须声明抛出RemoteException异常,这是因为远程方法调用与生俱来就缺乏本地调用的可靠性,远程调用总是存在失败的可能。例如,服务器或者网络连接不可用等。客户端代码必须时刻准备好处理这些问题。基于这些原因,Java编程语言要求每一次远程方法调用都必须捕获RemoteException,并且说明当调用不成功时相应的处理操作。
接下来,在服务器端,必须提供这样的类,它真正实现了在远程接口的声明的服务。
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.Map;
public class WarehouseImpl extends UnicastRemoteObjectimplements Warehouse {
privatestatic final long serialVersionUID = 1L;
privateMap<String, Double> prices;
protectedWarehouseImpl() throws RemoteException {
prices= new HashMap<String, Double>();
prices.put("xiaobug", 1.23);
prices.put("coder",123.456);
}
@Override
publicdouble getPrice(String description) throws RemoteException {
Doubleprice = prices.get(description);
return(price == null) ? 0 : price.doubleValue();
}
}
注意:WarehouseImpl的构造器声明了会抛出RemoteException异常,因为其超类的构造器会抛出这个异常。
可以看出这个类是远程方法调用的目标,因为它继承自UnicastRemoteObject,这个类的构造器使得它的对象可供远程访问。
有时候可能不希望服务器类继承UnicastRemoteObject,也许是因为实现类已经继承了其他的类。在这种情况下,我们需要亲自初始化远程对象,并将它们传给静态的exportObject方法。如果不继承UnicastRemoteObject,可以在远程对象的构造器中像下面这样调用exportObject方法。
UnicastRemoteObject.exportObject(this,0);
第二个参数是0,表明任意合适的端口都可用来监听客户连接。
3.2 RMI注册表
要访问服务器上的一个远程对象时,客户端首先需要一个本地的存根对象。可是客户端如何对该存根发出请求呢?最普通的方法是调用另一个服务对象上的一个远程方法,以返回值的方式取得存根对象。然而,这就成了先有鸡还是先有蛋的问题。第一个远程对象总要通过某种方式进行定位。为此,JDK提供了自举注册服务。
服务器程序使用自举注册服务来注册至少一个远程对象。要注册一个远程对象,需要一个RMI URL和一个对实现对象的引用。
RMI的URL以rmi:开头,后接服务器以及一个可选的端口号,接着是远程对象的名字。例如:
rmi://regserver.mycompany.com:99/central_warehouse
默认情况下,主机名是localhost,端口为1099。服务器告诉注册表在给定位置将该对象关联或“绑定”到这个名字。
下面的代码将一个WarehouseImpl对象注册到了同一个服务器上的RMI注册表中:
WarehouseImpl centralWarehouse = newWarehouseImpl();
Context namingContext = new InitialContext();
namingContext.bind("rmi:central_warehouse",centralWarehouse);
完整程序如下:
import java.rmi.RemoteException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class WarehouseServer {
publicstatic void main(String[] args) throws RemoteException,
NamingException{
System.out.println("Constructingserver implementation...");
Warehousewarehouse = new WarehouseImpl();
System.out.println("Bindingserver implementation to registry...");
Context namingContext= new InitialContext();
namingContext.bind("rmi:central_warehouse",warehouse);
System.out.println("Waitingfor invocations from clients...");
}
}
注意:基于安全原因,一个应用只有当它与注册表运行在同一个服务器时,该应用可以绑定、取消绑定,或重绑定注册对象的引用。
下面的程序展示了客户端如何获得远程仓库对象的存根,并调用远程的getPrice方法。
import java.rmi.RemoteException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class WarehouseClient {
publicstatic void main(String[] args) throws NamingException,
RemoteException{
Contextcontext = new InitialContext();
Stringurl = "rmi://localhost:1099/central_warehouse";
Warehousewarehouse = (Warehouse) context.lookup(url);
Stringdescription = "xiao bug";
doubleprice = warehouse.getPrice(description);
System.out.println(description+ " prices: " + price);
}
}
控制流图如下:
3.3 部署程序
部署一个使用RMI的应用是比较棘手的。按照客户端和服务器端将类进行分隔,从而在真实条件下对部署进行测试是很有价值的。
创建两个目录,分别存放用于启动服务器和客户端的类。
server/
WarehouseService.class
Warehouse.class
WarehouseImpl.class
client/
WarehouseClient.class
Warehouse.class
当部署RMI应用时,通常需要动态地将类交付给运行程序,其中一个例子就是RMI注册表(注册表的一个实例要服务许多不同的RMI应用)。RMI注册表需要访问注册的服务接口的类文件,但是,当注册表启动时,无法预测将来会产生的所有注册请求。因此,RMI注册表会动态地加载之前从未遇到过的所有远程接口的类文件。
动态交付的类文件是通过标准的Web服务器发布的。在我们的情况中,服务器程序需要使Warehouse.class文件对于RMI注册表来说是可获得的,因此我们将这个文件放到了第三个为download的目录中:
download/
Warehouse.class
我们使用Web服务器来服务这个目录中的内容。
当应用被部署时,服务器、RMI注册表、Web服务器和客户端可以定位到四台不同的计算机上。
注意:由于安全的原因,作为JDK一部分的rmiregistry服务只允许来自同一台主机的绑定调用。也就是说,服务器和rmiregistry进程需要定位在同一台计算机上。但是,RMI架构允许更通用的支持多台服务器的RMI注册表实现。
NanoHTTPD Web服务器是一个在Java源文件中实现的小型Web服务器。打开一个新的控制台窗口,转到download目录,然后将NanoHTTP.java复制到这个目录中。使用下面的命令来编译该源文件并启动这个Web服务器。
java NanoHTTPD -p 8080
其中,命令参数行参数是端口号。
接下来,打开另一个控制台窗口,转到不包含任何类文件的某个目录,并启动RMI注册表:
rmiregistry
打开第三个控制台窗口,转到server目录,并执行下面的命令:
java-Djava.rmi.server.codebase=http://localhost:8080/ WarehouseServer
java.rmi.server.codebase属性指出了服务类文件的URL。服务器程序将这个URL传递给RMI注册表。
警告:确保代码基URL以斜杠“/”结尾非常重要。
最后,打开第四个控制台窗口,转到client目录,运行:
java WarehouseClient
注意:如果只想测试基本的程序逻辑,那么可以将客户端和服务器的类文件放在同一个目录下。然后在这个目录中启动RMI注册表、服务器和客户端。
4. 远程方法中的参数和返回值
在开始进行远程方法调用时,调用参数需要从客户端的虚拟机中移动到服务器的虚拟机中。在调用完成之后,返回值需要进行反方向传递。对于从一个虚拟机向另一个虚拟机传值,我们将其分成两种情况:传递远程对象和传递非远程对象。例如,假设WarehouseServer的客户端将一个对Warehouse的引用(即,通过它可以调用远程的仓库对象的一个存根)传递给了另一个远程方法,这就是传递远程对象的实例。但是,大多数的方法参数都是普通的Java对象,而不是远程对象的引用。
4.1 传递远程对象
当一个远程对象的引用从一个虚拟机传递到另一个虚拟机时,该远程对象的发送者和接收者都将持有一个对同一个实体的引用。这个引用并非是一个内存位置(内存位置在单个虚拟机内都有意义),而是由网络地址和该远程对象的惟一标识符构成的。这个信息封装在存根对象中。
4.2 传递非远程对象
RMI使用序列化机制经由网络连接发送对象。这意味着任何实现了Serializable接口的类都可以用作参数和返回类型。
通过序列化来传递参数对远程方法的语义会产生很微妙的影响。当我们将对象传递给一个本地方法时,传递的是对象引用。如果该方法用某个修改方法对这个参数对象进行了操作,那么调用者会观察到这个变化。但是如果远程方法修改了某个序列化的参数,它修改的只是传递给它的副本,而调用者不会看到这种修改。
总结一下,在虚拟机之间传递值有两种机制:
实现了Remote接口的类的对象将作为远程引用传递。
实现Serializable接口,但是没有实现Remote接口的类的对象将使用序列化机制进行传递。
4.3 动态类加载
有时候,客户端需要拥有在运行时加载额外类的能力。
客户端使用了与RMI注册表相同的机制,即类由Web服务器提供服务,RMI服务器类将URL传递给客户端,客户端创建要求下载这些类的HTTP请求。
只要一个程序从网络位置加载新的代码,就会涉及安全问题。正是由于这个原因,我们需要在动态加载类的RMI应用中使用安全管理器。
使用RMI的程序应该安装一个安全管理器,去控制动态加载类的行为。可以用下面的指令安装:
System.setSecurityManager(newSecurityManager());
默认情况下,SecurityManager会限制程序中所有的代码去建立网络连接,但是,我们的程序需要建立到三个远程位置的网络连接:
Ø 加载远程类的Web服务器;
Ø RMI注册表;
Ø 远程对象;
为了允许这些操作,需要提供一个策略文件,如下所示:
grant{
permissionjava.net.SocketPermission "*:*","accept,connect,resolve";
};
需要通过将java.security.policy属性设置为这个策略文件名,来指示安全管理器去读取该策略文件。可以使用下面这个调用。
System.setProperty("java.security.policy","rmi.policy");
示例程序实现根据Product的description查询相应的Product(Product有两种类型:Phone、Computer),如果可以查询到,调用相应Product的getMessage方法,否则,打印“product is null”。
Product:
public abstract class Product implements Serializable {
privatestatic final long serialVersionUID = 1L;
publicabstract String getDescription();
publicabstract String getMessage();
}
Phone:
public class Phone extends Product {
privatestatic final long serialVersionUID = 1L;
privateString brand;
privateString type;
privatedouble price;
publicPhone(String brand, String type, double price) {
this.brand= brand;
this.type= type;
this.price= price;
}
publicString getBrand() {
returnbrand;
}
publicvoid setBrand(String brand) {
this.brand= brand;
}
publicString getType() {
returntype;
}
publicvoid setType(String type) {
this.type= type;
}
publicdouble getPrice() {
returnprice;
}
publicvoid setPrice(double price) {
this.price= price;
}
@Override
publicString getDescription() {
returnthis.getType();
}
@Override
publicString getMessage() {
returnthis.getBrand() + "\t" + this.getType() + "\t" +this.getPrice();
}
}
Computer:
public class Computer extends Product {
privatestatic final long serialVersionUID = 1L;
privateString brand;
privateString type;
privatedouble price;
publicComputer(String brand, String type, double price) {
this.brand= brand;
this.type= type;
this.price= price;
}
publicString getBrand() {
returnbrand;
}
publicvoid setBrand(String brand) {
this.brand= brand;
}
publicString getType() {
returntype;
}
publicvoid setType(String type) {
this.type= type;
}
publicdouble getPrice() {
returnprice;
}
publicvoid setPrice(double price) {
this.price= price;
}
@Override
publicString getDescription() {
returnthis.getType();
}
@Override
publicString getMessage() {
returnthis.getBrand() + "\t" + this.getType() + "\t" +this.getPrice();
}
}
ProductService:
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface ProductService extends Remote {
publicProduct getProduct(String description) throws RemoteException;
}
ProductServiceImpl:
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.List;
public class ProductServiceImpl extendsUnicastRemoteObject implements
ProductService{
privatestatic final long serialVersionUID = 1L;
privateList<Product> products;
protectedProductServiceImpl() throws RemoteException {
products= new ArrayList<Product>();
Computerc1 = new Computer("Dell", "灵越",5000.0);
Computerc2 = new Computer("三星", "小猪", 7000.0);
Phonep1 = new Phone("iPhone", "4S", 5000.0);
Phonep2 = new Phone("NOKIA", "E63", 1500.0);
products.add(c1);
products.add(c2);
products.add(p1);
products.add(p2);
}
@Override
publicProduct getProduct(String description) throws RemoteException {
Productproduct = null;
for(Product p : products) {
if(p.getDescription().equals(description)) {
product= p;
break;
}
}
returnproduct;
}
}
策略文件:
grant{
permissionjava.net.SocketPermission "*:*","accept,connect,resolve";
};
Client:
import java.rmi.RemoteException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class Client {
publicstatic void main(String[] args) throws NamingException,
RemoteException{
System.setProperty("java.security.policy","rmi.policy");
System.setSecurityManager(newSecurityManager());
Contextcontext = new InitialContext();
ProductServiceproductService = (ProductService) context
.lookup("rmi://localhost:1099/productService");
Productproduct = productService.getProduct("E63");
if(product!= null){
System.out.println(product.getMessage());
}else{
System.out.println("productis null");
}
}
}
Server:
import java.rmi.RemoteException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class Server {
publicstatic void main(String[] args) throws RemoteException,
NamingException{
System.setProperty("java.security.policy","rmi.policy");
System.setSecurityManager(newSecurityManager());
System.out.println("ConstructingServer Implementation...");
ProductServiceproductService = new ProductServiceImpl();
System.out.println("BindingServer Implementation to registry...");
Contextcontext = new InitialContext();
context.bind("rmi:productService",productService);
System.out.println("WaitingFor Invocations From Clients");
}
}
部署目录如下:
client/
Client.class
ProductService.class
Product.class
rmi.policy
server/
Server.class
ProductService.class
ProductServiceImpl.class
Product.class
Phone.class
Computer.class
rmi.policy
download/
ProductService.class
Product.class
Phone.class
Computer.class
NanoHTTP*.class
运行方法如前所示。
4.4 具有多重接口的远程引用
一个远程类可以实现多个接口。考虑远程接口ServiceCenter。
public interface ServiceCenter extends Remote {
intgetReturnAuthorization(Product prod) throws RemoteException;
}
现在假设WarehouseImpl类实现了这个接口与Warehouse接口。当一个对这种服务的引用被传递到另一个虚拟机时,接收者会获得一个可以访问在ServiceCenter与Warehouse接口中的所有远程方法的存根。可以使用instanceof操作符来查看一个特定的远程对象是否实现了某个接口。
4.5 远程对象与equals、hashCode和clone方法
存入集中的对象必须覆写equals方法。如果是散列集或散列映射表,则需定义hashCode方法。然而,比较远程对象时有一个问题。如果要比较两个远程对象是否具有相同的内容,调用equals则必须联系这些包含这些对象的服务器,然后比较它们的内容。而该调用可能会失败。但是,Object类中的equals方法并未声明会抛出RemoteException异常,而远程接口中的所有方法都必须抛出该异常。因为子类的方法不能比它重写的父类的方法抛出更多的异常,所以不能在远程接口中定义equals方法。hashCode也是这样。
相反,存根对象的equals和hashCode方法只是查看远程对象的位置。只要它们指向相同的远程对象,equals方法就认为两个存根相同。指向不同远程对象的存根绝不可能相同,即便那两个对象有一样的内容。相似的,散列码只能通过对象的标识符来计算。
由于相同的技术上的原因,远程引用也没有clone方法。如果真的可以调用clone方法,让服务器克隆出一个远程对象,那么clone方法就有可能抛出RemoteException异常。然而,在根类Object中定义clone方法时,就已经保证绝对不会抛出CloneNotSupportedException之外的任何异常。
总之,可以使用集与散列表中的远程引用,但是必须记住,对它们进行等价测试以及散列计算并不会考虑远程对象的内容。而且不能直接克隆远程引用。
5. 远程对象激活
在前面的例子中,我们使用了一个服务器程序来初始化和注册对象,以便客户端能够对它们进行远程调用。然而,在某些情况下,初始化大量的远程对象,是一种浪费,因为无论客户端是否使用它们,它们都在一直等待连接。激活机制(activation)允许延迟构造远程对象,仅当至少有一个客户端调用远程对象上的远程方法时,才真正去构造该远程对象。
要享用激活机制的好处,客户端代码完全无需改动。客户端只是简单的请求一个远程引用,然后通过它进行调用而已。
然而,服务器程序则需由一个激活程序来代替。该程序构造了对象的激活描述符(activation descriptors),这样的对象可以延迟构造,并且该程序通过命名服务为远程方法调用绑定接收者。第一次对这样的对象进行方法调用时,激活描述符中的信息将会用来构造该对象。
这样的远程对象必须继承Activatable类而不是UnicastRemoteObject类,当然,还需实现一个或多个远程接口。例如,
class WarehouseImpl extends Activatableimplements Warehouse {
...
}
因为对象的构造是延迟进行的,所以它必须以标准方式实现。因此,构造器必须包含以下两个参数:
Ø 一个激活ID(只需传递给父类的构造器)。
Ø 一个包含所有构造信息的对象,包装为MarshalledObject。
如果需要多个构造参数,必须将它们打包为一个对象。通常可以使用一个Object[]数组或一个ArrayList来达到此目的。
在构建激活描述符时,需要像下面这样从构造信息中构造一个MarshalledObject:
MarshalledObject<T> param = newMarshalledObject<T>(constructionInfo);
在实现对象的构造器中,使用MarshalledObject类的get方法来获得反序列化之后的构造信息:
T constructionInfo = param.get();
为了演示激活,修改了WarehouseImpl类,使得构造信息是一个由描述和价格构成的映射表。这个信息被封装到MarshalledObject中,并且在构造器中拆包出来:
public WarehouseImpl(ActivationIDid,MarshalledObject<Map<String,Double>> param) throwsRemoteException,ClassNotFoundException,IOException {
super(id,0);
prices =param.get();
System.out.println("Warehouseimplementation constructed.");
}
将0作为父类构造器的第二个参数,代表RMI类库应该分配一个合适的端口作为监听端口。
注意:其实远程对象不是一定要继承Activatable类,例如,可以将下面的静态方法调用放在服务器类的构造器中。
Activatable.exportObject(this,id,0);
现在,我们来看激活程序。
首先,需要定义一个激活组。一个激活组描述了启动远程对象所在的虚拟机所需的公共参数,其中最重要的参数是安全策略。
然后如下构造一个激活组描述符:
Properties props = new Properties();
props.put("java.security.policy","/path/to/server.policy");
ActivationGroupDesc group = newActivationGroupDesc(props,null);
第二个参数描述了一个特殊的命令选项,在这个例子中不需要任何选项,所以传递了一个null引用。
接下来,创建一个组ID
ActivationGroupID id =ActivationGroup.getSystem().registerGroup(group);
现在就可以构造一个激活描述符了。对于需要构造的每一个对象,都应该包括:
Ø 激活组ID,对象将在与其对应的虚拟机上被构造;
Ø 类的名称(例如“ProductImpl”或“com.mycompany.MyClassImpl”);
Ø URL字符串,由该URL加载类文件。这应该是基本URL,不含包的路径;
Ø 编组后的构造信息。
例如:
MarshalledObject param = newMarshalledObject(constructionInfo);
ActivationDesc desc = newActivationDesc(id,"WarehouseImpl","http://myserver.com/download",param);
将此描述符传递给静态的Activatable.register方法。它返回一个对象,该对象实现了实现类的远程接口。可以使用命名服务绑定该对象:
Warehouse centralWarehouse =(Warehouse)Activatable.register(desc);
namingContext.bind("rmi:central_warehouse",centralWarehouse);
与前例的服务器程序不同,激活程序在注册与绑定激活接收者之后就会退出。仅当远程方法调用第一次发生时,才会构造远程对象。
Warehouse:
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Warehouse extends Remote {
public doublegetPrice(String description) throws RemoteException;
}
WarehouseImpl:
import java.io.IOException;
import java.rmi.MarshalledObject;
import java.rmi.RemoteException;
import java.rmi.activation.Activatable;
import java.rmi.activation.ActivationID;
import java.util.Map;
public class WarehouseImpl extends Activatable implementsWarehouse {
private staticfinal long serialVersionUID = 1L;
privateMap<String, Double> prices;
publicWarehouseImpl(ActivationID id,
MarshalledObject<Map<String,Double>> prices) throws IOException,
ClassNotFoundException{
super(id,0);
this.prices= prices.get();
System.out.println("Warehouseimplementation constructed.");
}
@Override
public doublegetPrice(String description) throws RemoteException {
Doubleprice = prices.get(description);
return(price == null) ? 0 : price.doubleValue();
}
}
Server:
import java.io.File;
import java.io.IOException;
import java.rmi.MarshalledObject;
import java.rmi.activation.Activatable;
import java.rmi.activation.ActivationDesc;
import java.rmi.activation.ActivationException;
import java.rmi.activation.ActivationGroup;
import java.rmi.activation.ActivationGroupDesc;
import java.rmi.activation.ActivationGroupID;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class Server {
public staticvoid main(String[] args) throws NamingException, IOException,
ActivationException{
System.out.println("Constructingactivation descriptors...");
Propertiespro = new Properties();
pro.put("java.security.policy",new File("rmid.policy")
.getCanonicalPath());
ActivationGroupDescgroupDesc = new ActivationGroupDesc(pro, null);
ActivationGroupIDgroupID = ActivationGroup.getSystem().registerGroup(
groupDesc);
Map<String,Double> prices = new HashMap<String, Double>();
prices.put("xiaobug", 1.23);
prices.put("coder",123.456);
MarshalledObject<Map<String,Double>> param = new MarshalledObject<Map<String, Double>>(
prices);
Stringcodebase = "http://localhost:8080/";
ActivationDescactivationDesc = new ActivationDesc(groupID,
"WarehouseImpl",codebase, param);
Warehousewarehouse = (Warehouse) Activatable.register(activationDesc);
System.out.println("Bindingserver implementation to registry...");
ContextnamingContext = new InitialContext();
namingContext.bind("rmi:central_warehouse",warehouse);
System.out.println("Exiting...");
}
}
Client:
import java.rmi.RemoteException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class Client {
public staticvoid main(String[] args) throws NamingException,
RemoteException{
Contextcontext = new InitialContext();
Stringurl = "rmi://localhost:1099/central_warehouse";
Warehousewarehouse = (Warehouse) context.lookup(url);
Stringdescription = "xiao bug";
doubleprice = warehouse.getPrice(description);
System.out.println(description+ " prices: " + price);
}
}
安全策略文件:
grant {
permissionjava.net.SocketPermission "*:*","accept,connect,resolve";
permissioncom.sun.rmi.rmid.ExecPermission "${java.home}${/}bin${/}java";
permissioncom.sun.rmi.rmid.ExecOptionPermission "-Djava.security.policy=*";
};
部署目录如下:
client/
WarehouseClient.class
Warehouse.class
server/
Server.class
Warehouse.class
WarehouseImpl.class
rmid.policy
download/
Warehouse.class
WarehouseImpl.class
rmi/
rmid.policy
启动程序步骤如下:
(1)在rmi目录(它不包含任何类文件)中启动RMI注册表;
(2)在rmi目录中启动RMI激活守护程序;
rmid -J-Djava.security.policy=rmid.policy
rmid程序监听激活请求,并且激活另一个虚拟机中的对象。要启动一个虚拟机,rmid程序需要一定的权限。这些权限在安全策略文件中已经说明了。使用-J,可以传递一个选项给运行激活守护程序的虚拟机。
(3)在download目录中启动NanoHTTP Web服务器;
(4)从server目录运行激活程序;
java-Djava.rmi.server.codebase=http://localhost:8080/ Server
在使用命名服务对激活接收者进行注册之后,该程序就会退出。
(5)从client目录运行客户端程序。
java Client
第一次运行客户端程序时,在激活守护程序所在的shell窗口中还可以看到构造器的消息。