3.2.2 编译源程序
首先将两个接口类Compute.class和Task.class打成JAR包(compute.jar)传到客户端,或者向客户端添加类路径也行。这样客户端的类才能正确编译。编译过程不在详述。
需要被其他JVM动态加载的类要能够通过网络远程访问到,因此可以将它们放在一个Web服务器上,通过HTTP访问。当然在本机上测试时也可以使用文件路径,在此使用文件路径,如file:/c:\home\user\public_html\classes/
在本示例中注册处需要动态从服务器端加载Compute.class,Task.class和ComputeEngine_Stub.class。
存根类ComputeEngine_Stub是通过rmic命令生成的,在Dos窗口中,到ComputeEngine所在目录(注意包路径不包含在内),运行
rmic engine. ComputeEngine 即可自动生成ComputeEngine_Stub.class。
注意此例中存根类是必须的,因为我们使用的是public static RemoteStub exportObject(Remote obj) throws RemoteException方法导出远程对象的,如果采用其它方法则可以不需要,临时动态生成动态代理(JDK5.0版本后)。
服务器端需要动态从客户端加载类Pi.class。
客户端需要动态从服务器端加载类ComputeEngine_Stub.class,而Compute.class和Task.class本地路径中已有。
3.2.3 运行源程序
服务器端和客户端在运行时都需要加载一个安全管理器,因此我们首先需要编写两个安全策略文件,允许访问链接,这里使用最简单的策略文件。
grant {
permission java.security.AllPermission;
};
其中客户端策略文件client.policy和服务器端策略文件server.policy是相同的。
1. 启动注册表
在Dos窗口中运行 rmiregistry,此命令是JDK提供的。
2. 启动服务器 (命令如下,Windows环境)
java -cp c:\home\ann\src;c:\home\ann\public_html\classes\compute.jar
-Djava.rmi.server.codebase=file:/c:\home\ann\public_html\classes\compute.jar
-Djava.rmi.server.hostname=localhost
-Djava.security.policy=server.policy
engine.ComputeEngine
java.rmi.server.codebase 指定了一个地址,所需要的类定义可以从此地址下载。
java.rmi.server.hostname 指定了存放远程对象所对应存根对象的主机名称,客户端在尝试远程对象方法调用时将用到此地址。
java.security.policy 指定了所使用的安全策略文件。
3. 启动客户端 (命令如下,Windows环境)
java -cp c:\home\jones\src;c:\home\jones\public_html\classes\compute.jar
-Djava.rmi.server.codebase=file:/c:/home/jones/public_html/classes/
-Djava.security.policy=client.policy
client.ComputePi localhost 45
运行后会得到如下结果:
3.141592653589793238462643383279502884197169399
下图描述了在程序执行过程中rmiregistry,ComputeEngine server,ComputePi client在哪里获得类定义。
图3.3 类加载图
四、JDK5.0中的动态代理类
4.1 JDK5.0中RMI的新特性
如果是在JDK5.0下运行程序会发现一些有意思的事情,如不生成远程对象的存根文件,程序在运行时不会报错。仍然可以正确的运行得到结果(这一点困扰我很长时间)。这是因为在JDK5.0以前的版本中,需要用rmic命令来为远程对象生成静态的代理类(包括存根和骨架类),而在JDK5.0中,RMI框架会在运行时自动为远程对象生成动态代理类(包括存根和骨架类),从而更彻底的封装的RMI框架的实现细节,简化了RMI框架的使用方式。
在JDK5.0中,如果无法加载远程对象的stub类或将系统属性 java.rmi.server.ignoreStubClasses 设置为 "true",并且我们使用除public static RemoteStub exportObject(Remote obj) throws RemoteException方法外的其它重载方法导出远程对象时,程序运行时会构造一个远程对象的动态代理,是类java.lang.reflect.Proxy的一个实例,这样在向注册表注册时传递给注册表的是一个Proxy对象,而不是ComputeEngine_Stub对象,因为Proxy类在JDK中已有类定义,因此注册处并不需要从服务器端加载Proxy类定义,但Compute类和Task类还是需要从服务器端加载的。
同样的道理,客户端从注册处获得Proxy动态代理对象后,也不需要从服务器端获得其类定义,从而客户端不会从服务器端动态加载任何类定义,也就是说客户端的安全管理器可以省略。但服务器端仍然需要动态从客户端加载Pi类定义,从而服务器端的安全管理器不能省略。
但是一旦生成了ComputeEngine_Stub存根类,那么程序就不会动态生成动态代理,那么整个运行过程就如3.2中所述。
4.2 JDK5.0中的动态代理类
动态代理类的字节码在程序运行时由Java反射机制动态生成,无须程序员手工编写它的源代码。java.lang.reflect包中的Proxy类和InvocationHandler接口提供了生成动态代理类的能力。
Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
l public static Class<?> getProxyClass(ClassLoader loader,Class<?>[] interfaces) throws IllegalArgumentException 静态方法负责创建动态代理类,并向其提供类加载器和接口数组。该代理类将由指定的类加载器定义,并将实现提供的所有接口。如果类加载器已经定义了具有相同排列接口的代理类,那么现有的代理类将被返回;否则,类加载器将动态生成并定义这些接口的代理类。
l public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException 静态方法返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。此方法相当于:Proxy.getProxyClass(loader, interfaces).getConstructor(new Class[] { InvocationHandler.class }).newInstance(new Object[] { handler });
由Proxy类的静态方法创建的动态代理类(以下成为代理类)具有以下特点:
l 代理类是公共的、最终的,和非抽象类型的。
l 未指定代理类的非限定名称。但是,以字符串 "$Proxy" 开头的类名空间应该为代理类保留。
l 代理类继承了 java.lang.reflect.Proxy类。
l 代理类会按同一顺序准确地实现其创建时指定的接口。
l 如果代理类实现了非公共接口,那么它将在与该接口相同的包中定义。否则,代理类的包也是未指定的。注意,包密封将不阻止代理类在运行时在特定包中的成功定义,也不会阻止相同类加载器和带有特定签名的包所定义的类。
l 由于代理类将实现所有在其创建时指定的接口,所以对其 Class 对象调用 getInterfaces 将返回一个包含相同接口列表的数组(按其创建时指定的顺序),对其 Class 对象调用 getMethods 将返回一个包括这些接口中所有方法的 Method 对象的数组,并且调用 getMethod 将会在代理接口中找到期望的一些方法。
l Proxy类的isProxyClass(Class<?> cl) 静态方法可用来判断参数指定的类是否为动态代理类。只有由 Proxy.getProxyClass 返回的类,或由 Proxy.newProxyInstance 返回的对象的类才是动态代理类。
l 代理类的 java.security.ProtectionDomain 与由引导类加载器(如 java.lang.Object)加载的系统类相同,原因是代理类的代码由受信任的系统代码生成。此保护域通常被授予 java.security.AllPermission。
l 每个代理类都有一个可以带一个参数(接口 InvocationHandler 的实现)的公共构造方法,用于设置代理实例的调用处理程序。并非必须使用反射 API 才能访问公共构造方法,通过调用 Proxy.newInstance 方法(将调用 Proxy.getProxyClass 的操作和调用带有调用处理程序的构造方法结合在一起)也可以创建代理实例。
l 每个代理类实例都和一个InvocationHandler实例关联。Proxy类的getInvocationHandler(Object proxy)静态方法返回与参数proxy指定的代理类实例所关联的InvocationHandler对象。当调用代理类所实现接口的方法时,该方法会调用与它关联的InvocationHandler对象的invoke()方法。
InvacationHandler接口为方法调用接口,它声明了负责调用任意一个方法的invoke()方法:Object invoke(Object proxy, Method method, Object[] args) throws Throwable,参数proxy指定动态代理类实例,参数method指定被调用的方法,参数args指定向被调用方法传递的参数,invoke方法的返回值表示被调用方法的返回值。
五、Java RMI总结
5.1 Java RMI的优点
RMI机制给分布计算的系统设计、编程都带来了极大的方便。只要按照RMI规则设计程序,可以不必再过问在RMI之下的网络细节了,如:TCP和Socket等等。任意两台计算机之间的通讯完全由RMI负责。调用远程计算机上的对象就像本地对象一样方便。
1. 面向对象:
RMI可将完整的对象作为参数和返回值进行传递,而不仅仅是预定义的数据类型。也就是说,可以将类似Java哈西表这样的复杂类型作为一个参数进行传递。
2. 可移动属性:
RMI可将属性从客户机移动到服务器,或者从服务器移动到客户机。
3. 设计方式:
对象传递功能使您可以在分布式计算中充分利用面向对象技术的强大功能,如二层和三层结构系统。如果用户能够传递属性,那么就可以在自己的解决方案中使用面向对象的设计方式。所有面向对象的设计方式无不依靠不同的属性来发挥功能,如果不能传递完整的对象,包括实现和类型,就会失去设计方式上所提供的优点。
4. 安全性:
RMI使用Java内置的安全机制保证下载执行程序时用户系统的安全。RMI使用专门为保护系统免遭恶意小程序侵害而设计的安全管理程序。
5. 便于编写和使用
RMI使得Java远程服务程序和访问这些服务程序的Java客户程序的编写工作变得轻松、简单。远程接口实际上就是Java接口。为了实现RMI的功能必须创建远程对象任何可以被远程调用的对象必须实现远程接口。但远程接口本身并不包含任何方法。因而需要创建一个新的接口来扩展远程接口。新接口将包含所有可以远程调用的方法。远程对象必须实现这个新接口,由于新的接口扩展了远程接口,实现了新接口,就满足了远程对象对实现远程接口的要求,所实现的每个对象都将作为远程对象引用。
5.2 Java RMI的缺点
RMI虽然编写起来比较方便,但运行的效率较低,而且RMI局限于Java之间的通信,无法与其他的语言平台进行通信。因此出现了RMI-IIOP技术,RMI-IIOP综合了RMI的简单性和CORBA的多语言性(兼容性),其次RMI-IIOP克服了RMI只能用于Java的缺点和CORBA的复杂性(可以不用掌握IDL)。
参考文献
[1] 李华琰、郭英奎 著:Java 中间件开发技术 . 中国水利水电出版社,2005
[2] David Reilly , Michael Reilly著,沈凤等译.Java网络编程与分布式计算.北京:机械工业出版社,2003
[3]孙卫琴 著.Java网络编程精解.北京:电子工业出版社,2007
[4] Trail RMI (The Java™ Tutorials).2007
http://java.sun.com/docs/books/tutorial/rmi/index.html
[5] JDK5.0 中文文档
Author: orangelizq
email: orangelizq@163.com
posted on 2007-12-01 21:01
桔子汁 阅读(1768)
评论(0) 编辑 收藏 所属分类:
J2SE