原文地址: http://java.sun.com/docs/books/tutorial/rmi/index.html 译者:jht
欢迎大家对翻译中存在的问题批判指正,谢谢大家!
从Word粘帖过来之后格式有点问题,所以把word文档传上来了,点击下载
RMI
Trial: RMI
Java
远程方法调用(
RMI
)机制允许运行在一个
Java
虚拟机的对象调用运行在另一个
Java
虚拟机里对象提供的方法。
RMI
提供了
Java
语言程序之间的远程通信功能。
注释
:
如果你要和现存的
IDL
程序打交道,
那就应该使用
Java IDL
而不是
RMI
。
The Java Remote Method Invocation (RMI) system allows an object running in one Java virtual machine to invoke methods on an object running in another Java virtual machine. RMI provides for remote communication between programs written in the Java programming language.
Note: If you are connecting to an existing IDL program, you should use Java IDL rather than RMI.
本系列文章首先介绍一下
RMI
系统,然后从头到尾实现一个
RMI
客户端
/
服务器的示例程序,使用
RMI
的特性在运行时加载并执行用户定义的任务。例中的服务端程序实现一个普通的计算器,客户端程序使用这个服务端提供的计算器功能计算
值
.
- RMI 应用概述:
这一节描述什么是
RMI
系统,
RMI
系统的优点,除此之外还将描述一个典型的
RMI
应用由一个服务端和一个客户端组成,介绍一些重要的术语。
- 写一个 RMI 服务端程序:
这一节详细讲解
compute engine
服务端程序的代码,教会你如何去设计和实现
RMI
服务端程序。
- 创建一个 RMI 客户端程序:
这一节以一个
compute engine
客户端程序为例来讲解
RMI
客户端程序的重要特性。
- 编译和运行示例程序:
向你展示如何编译运行
compute engine
的客户端和服务端程序
This trail provides a brief overview of the RMI system and then walks through a complete client/server example that uses RMI's unique capabilities to load and to execute user-defined tasks at runtime. The server in the example implements a generic compute engine, which the client uses to compute the value of .
-
An Overview of RMI Applications describes the RMI system and lists its advantages. Additionally, this section provides a description of a typical RMI application, composed of a server and a client, and introduces important terms.
-
Writing an RMI Server walks through the code for the compute engine server. This section will teach you how to design and to implement an RMI server.
-
Creating A Client Program takes a look at one possible compute engine client and uses it to illustrate the important features of an RMI client.
-
Compiling and Running the Example shows you how to compile and to run both the compute engine server and its client.
RMI 应用概述( An Overview of RMI Applications )
RMI
应用通常有两个分开的程序组成,一个服务端程序和一个客户端程序。一个典型的服务端程序创建一些远程对象,使得对这些远程对象的引用可以被访问,等待客户端调用这些远程对象提供的方法。一个典型的客户端程序获取远程引用,指向一个或者多个服务端上的远程对象,然后调用这些远程对象所提供的方法。通常我们称这为分布式对象应用程序。
RMI applications often comprise two separate programs, a server and a client. A typical server program creates some remote objects, makes references to these objects accessible, and waits for clients to invoke methods on these objects. A typical client program obtains a remote reference to one or more remote objects on a server and then invokes methods on them. RMI provides the mechanism by which the server and the client communicate and pass information back and forth. Such an application is sometimes referred to as a distributed object application.
分布式对象应用程序需要做的事情:
- 查找(定位)远程对象。
应用程序可以使用各种不同的机制取得远程对象的引用。比如应用程序可以通过
RMI
提供的简单的命名工具,
RMI
注册。或者应用程序可以传递和返回远程对象作为远程方法调用的一部分。
- 和远程对象通信。
远程对象之间通信的细节有
RMI
处理,对程序员来说远程对象的通信和通常的
Java
方法调用没有区别。
- 加载传递过来的远程对象的类定义。
因为
RMI
允许对象双向传递,它提供了加载对象类定义和传递对象数据的机制。
Distributed object applications need to do the following:
-
Locate remote objects.
Applications can use various mechanisms to obtain references to remote objects. For example, an application can register its remote objects with RMI's simple naming facility, the RMI registry. Alternatively, an application can pass and return remote object references as part of other remote invocations.
-
Communicate with remote objects.
Details of communication between remote objects are handled by RMI. To the programmer, remote communication looks similar to regular Java method invocations.
-
Load class definitions for objects that are passed around.
Because RMI enables objects to be passed back and forth, it provides mechanisms for loading an object's class definitions as well as for transmitting an object's data.
下面这幅插图描述了一个使用
RMI
注册机制取得远程对象引用的
RMI
分布式应用。
Server
调用注册机制讲一个名字和远程对象关联
(
或者叫绑定
)
。
Client
根据名字在
Server
的注册机制里面查找远程对象,调用找到的远程对象的方法。这个图还描述了这样的情况,
RMI
系统使用
Web server
,从
Server
到
Client
或者从
Client
到
Server
,加载所需对象的类定义。
The following illustration depicts an RMI distributed application that uses the RMI registry to obtain a reference to a remote object. The server calls the registry to associate (or bind) a name with a remote object. The client looks up the remote object by its name in the server's registry and then invokes a method on it. The illustration also shows that the RMI system uses an existing web server to load class definitions, from server to client and from client to server, for objects when needed.
动态代码加载的优势 ( Advantages of Dynamic Code Loading )
RMI
的一个独特的核心优势就是能够加载一个未在接收端的
Java
虚拟机中定义对象的类。之前在一个
Java
虚拟机中定义的一个对象所有的类型和行为,能够从这个
Java
虚拟机传递到另外一个
Java
虚拟机,这个
Java
虚拟机可以是远程的。
RMI
根据对象的实际的类传递对象,所以当对象被传递到另一个
Java
虚拟机的时候对象行为不会改变。这个能力使得新的对象类型和行为能够被引入到一个远程的
Java
虚拟机当中,也就是说动态扩展了应用程序的行为。这个系列教程中的
compute engine
的例子就是使用这种能力往分布式程序中引入新的行为。
One of the central and unique features of RMI is its ability to download the definition of an object's class if the class is not defined in the receiver's Java virtual machine. All of the types and behavior of an object, previously available only in a single Java virtual machine, can be transmitted to another, possibly remote, Java virtual machine. RMI passes objects by their actual classes, so the behavior of the objects is not changed when they are sent to another Java virtual machine. This capability enables new types and behaviors to be introduced into a remote Java virtual machine, thus dynamically extending the behavior of an application. The compute engine example in this trail uses this capability to introduce new behavior to a distributed program.
远程接口,对象,方法 ( Remote Interfaces, Objects, and Methods )
正如其他很多
Java
应用程序一样,一个构建在
RMI
之上的分布式应用也是由接口和类组成的。接口声明方法。类实现在接口里定义的方法,也许会声明额外的方法。在分布式应用中,一些方法可能存在于某些
Java
虚拟机中但是却不在另一个
Java
虚拟机中。提供能够被
Java
虚拟机之间调用的方法类被称作远程对象(
remote objects
)
.
Like any other Java application, a distributed application built by using Java RMI is made up of interfaces and classes. The interfaces declare methods. The classes implement the methods declared in the interfaces and, perhaps, declare additional methods as well. In a distributed application, some implementations might reside in some Java virtual machines but not others. Objects with methods that can be invoked across Java virtual machines are called remote objects.
一个普通对象通过实现远程接口(
remote interface
)变成远程对象,这个远程接口有如下特征。
-
一个远程接口扩展
java.rmi.Remote
接口
-
每个远程接口里声明的方法除了声明抛出本身应用特定的异常之外,都要声明抛出
java.rmi.RemoteException
异常
An object becomes remote by implementing a remote interface, which has the following characteristics:
-
A remote interface extends the interface java.rmi.Remote.
-
Each method of the interface declares java.rmi.RemoteException in its throws clause, in addition to any application-specific exceptions.
当对象在一个
Java
虚拟机传递到另一个
Java
虚拟机时,
RMI
区别对待远程对象和非远程对象。
RMI
传递一个远程对象的
stub
,而不是仅在接收的
JVM
里做一份对象的拷贝。这个
stub
担当远程对象的代表或者代理的角色,为
client
远程对象的引用服务。
Client
调用本地
stub
的一个方法,而这个
stub
则负责执行远程对象里这一个方法的调用。
RMI treats a remote object differently from a non-remote object when the object is passed from one Java virtual machine to another Java virtual machine. Rather than making a copy of the implementation object in the receiving Java virtual machine, RMI passes a remote stub for a remote object. The stub acts as the local representative, or proxy, for the remote object and basically is, to the client, the remote reference. The client invokes a method on the local stub, which is responsible for carrying out the method invocation on the remote object.
一个远程对象的
stub
实现了与这个远程对象所实现的远程接口的相同集合。这个特性使得
stub
能够被转型喝远程对象实现的任意一个接口。然而,也只有那些在远程接口里声明的方法才能被接收端的
JVM
调用。
A stub for a remote object implements the same set of remote interfaces that the remote object implements. This property enables a stub to be cast to any of the interfaces that the remote object implements. However, only those methods defined in a remote interface are available to be called from the receiving Java virtual machine.
通过使用 RMI 创建分布式应用 ( Creating Distributed Applications by Using RMI )
通过使用
RMI
开发一个分布式应用遵循下面几个步骤:
1.
设计实现分布式应用的组建
2.
编译源代码
3.
使得你的类在网络上可访问
4.
启动应用程序
Using RMI to develop a distributed application involves these general steps:
-
Designing and implementing the components of your distributed application.
-
Compiling sources.
-
Making classes network accessible.
-
Starting the application.
设计实现应用程序组件( Designing and Implementing the Application Components )
首先觉得你的应用体系结构,包括那个组件是本地对象,那个组件远程可访问。这一步骤包括:
-
定义远程接口
.
一个远程接口指定了哪些方法能够被
client
远程的调用。
Client
程序针对远程接口编程,而不是针对实现了这些远程接口的类。这些接口的设计包括了如何声明远程方法所需参数的对象类型,以及远程方法返回值类型。如何任何一个这些接口和类还不存在,你需要定义他们。
-
实现远程对象
.
远程对象需要实现至少一个远程接口。远程对象也可以实现其他的接口,这个接口里声明的方法只在本地
JVM
可用。如果任何一个本地类要被用作为这些(远程)方法的参数或者是返回值,这些类也需要被实现。
-
实现客户端
.
在远程接口定义好之后,使用远程对象的客户端可以在任何时候被实现,任何时候的意思包括在远程对象部属之后。
First, determine your application architecture, including which components are local objects and which components are remotely accessible. This step includes:
-
Defining the remote interfaces.
A remote interface specifies the methods that can be invoked remotely by a client. Clients program to remote interfaces, not to the implementation classes of those interfaces. The design of such interfaces includes the determination of the types of objects that will be used as the parameters and return values for these methods. If any of these interfaces or classes do not yet exist, you need to define them as well.
-
Implementing the remote objects.
Remote objects must implement one or more remote interfaces. The remote object class may include implementations of other interfaces and methods that are available only locally. If any local classes are to be used for parameters or return values of any of these methods, they must be implemented as well.
-
Implementing the clients.
Clients that use remote objects can be implemented at any time after the remote interfaces are defined, including after the remote objects have been deployed.
编译源代码( Compiling Sources )
正如任何
Java
程序一样,你可以使用
javac
命令编译源代码。源文件包含了远程接口的声明,这些接口的实现,其他的
server
类,还有
client
类。
注释
: Java Platform, Standard Edition 5.0
之前的
Java
版本
,
创建
stub
类之前,需要做一步额外的工作,就是使用
rmic
编译。然而现在我们不再需要这个步骤了。
As with any Java program, you use the javac compiler to compile the source files. The source files contain the declarations of the remote interfaces, their implementations, any other server classes, and the client classes.
Note: With versions prior to Java Platform, Standard Edition 5.0, an additional step was required to build stub classes, by using the rmic compiler. However, this step is no longer necessary.
使得类在网络上可访问( Making Classes Network Accessible )
这一步,你要使得某些类的定义在网络上能够访问。比如远程接口和与之相关的类型,需要被
client
或者
server
下载的类的声明。典型的大多是使用
Web Server
使类定义在网络上可访问的。
In this step, you make certain class definitions network accessible, such as the definitions for the remote interfaces and their associated types, and the definitions for classes that need to be downloaded to the clients or servers. Classes’ definitions are typically made network accessible through a web server.
启动应用程序( Starting the Application )
启动应用程序包括启动
RMI
远程对象注册,启动
Server
,启动
Client
。
这一节的余下内容我们讨论如何一步步创建一个
compute engine
Starting the application includes running the RMI remote object registry, the server, and the client.
The rest of this section walks through the steps used to create a compute engine.
创建一个普通的 Compute Engine ( Building a Generic Compute Engine )
这一节重点讲述一个简单但是强大的分布式应用程序
compute engine
。
compute engine
是一个服务器上的远程对象,从客户端接受任务,执行任务之后返回结果。这些任务是在服务端运行的机器上执行的。这种类型的分布式应用程序使得许多客户端使用性能强劲的机器或者是拥有特殊硬件资源的机器。
This trail focuses on a simple, yet powerful, distributed application called a compute engine. The compute engine is a remote object on the server that takes tasks from clients, runs the tasks, and returns any results. The tasks are run on the machine where the server is running. This type of distributed application can enable a number of client machines to make use of a particularly powerful machine or a machine that has specialized hardware.
Compute engine
奇特之处在于它运行的任务不需要在它写代码或者运行的时候定义。新的任务可以随时被创建然后交由
compute engine
执行。一个任务唯一的要求就是任务类必须实现一个特定的接口。需要完成任务的代码能够被
RMI
系统下载到
Compute engine
。然后
Compute engien
运行这个任务,使用
compute engine
所运行的机器上的资源。
The novel aspect of the compute engine is that the tasks it runs do not need to be defined when the compute engine is written or started. New kinds of tasks can be created at any time and then given to the compute engine to be run. The only requirement of a task is that its class implement a particular interface. The code needed to accomplish the task can be downloaded by the RMI system to the compute engine. Then, the compute engine runs the task, using the resources on the machine on which the compute engine is running.
执行任意任务的能力是由
Java
平台的动态特性保证的,这个动态特性又被
RMI
扩展到网络世界。
RMI
动态装载任务代码到
compute engine
所在的
JVM
,然后运行这个任务,而不需要预先知道实现这个任务的类。这样一个有动态加载代码能力的应用通常被称为
behavior-based application
。这种应用程序通常要求允许代理的基础结构。有了
RMI
,这种应用构成了
Java
平台上分布式计算的基本的机制。
The ability to perform arbitrary tasks is enabled by the dynamic nature of the Java platform, which is extended to the network by RMI. RMI dynamically loads the task code into the compute engine's Java virtual machine and runs the task without prior knowledge of the class that implements the task. Such an application, which has the ability to download code dynamically, is often called a behavior-based application. Such applications usually require full agent-enabled infrastructures. With RMI, such applications are part of the basic mechanisms for distributed computing on the Java platform.
写一个 RMI Server ( Writing an RMI Server )
compute engine server
接受客户端的任务,执行任务,返回结果。服务端代码由一个接口和一个类组成。接口定义了能够被客户端调用的方法,定义了
client
对远程对象的视图。服务端代码的类提供了接口的实现。
The compute engine server accepts tasks from clients, runs the tasks, and returns any results. The server code consists of an interface and a class. The interface defines the methods that can be invoked from the client. Essentially, the interface defines the client's view of the remote object. The class provides the implementation.
设计 Remote Interface
这一节解释
compute interface
,它提供了
client
和
server
的关联。你也将学习
RMI
支持这一通讯的
API
。
实现 Remote Interface
这一节探究实现
Compute interface
的类,由此实现一个远程对象。这个类同样提供了组成
server
程序的其余代码,包括创建远程对象实例,使用
RMI
注册机制注册,创建安全管理器的
main
方法。
Designing a Remote Interface
This section explains the Compute interface, which provides the connection between the client and the server. You will also learn about the RMI API, which supports this communication.
Implementing a Remote Interface
This section explores the class that implements the Compute interface, thereby implementing a remote object. This class also provides the rest of the code that makes up the server program, including a main method that creates an instance of the remote object, registers it with the RMI registry, and sets up a security manager.
设计 Remote Interface ( Designing a Remote Interface )
Compute engine
的核心是一个协议,这个协议允许任务提交到
compute engine
,然后
computer engine
执行这些提交过来任务,任务执行的结果被返回到
client
。这个协议在
compute engine
所支持的接口上表现出来。下图描绘了这个协议的远程通信。
At the core of the compute engine is a protocol that enables tasks to be submitted to the compute engine, the compute engine to run those tasks, and the results of those tasks to be returned to the client. This protocol is expressed in the interfaces that are supported by the compute engine. The remote communication for this protocol is illustrated in the following figure.
每个接口都有一个单一的方法,
Compute engine
的
remote interface
即
Compute
接口允许任务被提交到
compute engine
。
Client
端的
Task
接口定义了
compute engine
如何执行被提交的任务。
Each interface contains a single method. The compute engine's remote interface, Compute, enables tasks to be submitted to the engine. The client interface, Task, defines how the compute engine executes a submitted task.
compute.Compute
接口定义了远程可访问的部分,下面是
Compute
接口的源代码:
The compute.Compute interface defines the remotely accessible part, the compute engine itself. Here is the source code for the Compute interface:
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Compute extends Remote {
< T > T executeTask(Task < T > t) throws RemoteException;
}
通过继承
java.rmi.Remote
接口,
Compute
接口表明自己是接口方法可以被另一个
JVM
调用的接口。所以实现该接口的对象就是远程对象。
By extending the interface java.rmi.Remote, the Compute interface identifies itself as an interface whose methods can be invoked from another Java virtual machine. Any object that implements this interface can be a remote object.
作为远程接口的一员,
executeTask
方法是一个远程方法。因此该方法需要定义为能抛出
java.rmi.RemoteException
异常。这个异常由
RMI
系统调用一个远程方法时抛出,表示通讯失败或者是协议错误发生。
RemoteException
是一个受检查的异常,所以任何调用远程方法的代码需要处理这个异常,要么捕获该异常,要么声明抛出子句。
As a member of a remote interface, the executeTask method is a remote method. Therefore, this method must be defined as being capable of throwing a java.rmi.RemoteException. This exception is thrown by the RMI system from a remote method invocation to indicate that either a communication failure or a protocol error has occurred. A RemoteException is a checked exception, so any code invoking a remote method needs to handle this exception by either catching it or declaring it in its throws clause.
Compute engine
需要的第二个接口是
Task
接口,这个接口也是
executeTask
的类型参数。
Compute.Task
接口定义了
compute engine
和它要执行的工作直接的接口,提供了开始这个工作的方法。下面是
Task
接口的源代码:
The second interface needed for the compute engine is the Task interface, which is the type of the parameter to the executeTask method in the Compute interface. The compute.Task interface defines the interface between the compute engine and the work that it needs to do, providing the way to start the work. Here is the source code for the Task interface:
public interface Task < T > {
T execute();
}
Task
接口就定义了一个方法,
execute
,无参数,也没有异常。因为这个接口没有继承
Remote
接口,所以方法也无需声明抛出
java.rmi.RemoteException
子句。
The Task interface defines a single method, execute, which has no parameters and throws no exceptions. Because the interface does not extend Remote, the method in this interface doesn't need to list java.rmi.RemoteException in its throws clause.
Task
接口有一个类型参数,
T
,代表任务执行的返回结果。这个接口的
execute
方法返回计算的结果,因此它的返回类型是
T
。
The Task interface has a type parameter, T, which represents the result type of the task's computation. This interface's execute method returns the result of the computation and thus its return type is T.
Compute
接口的
executeTask
方法依次返回传递给它的
Task
实例执行的结果。因此
executeTask
方法有自己的类型参数,
T
,它将自身的返回值和
Task
实例的结果类型关联起来。
The Compute interface's executeTask method, in turn, returns the result of the execution of the Task instance passed to it. Thus, the executeTask method has its own type parameter, T, that associates its own return type with the result type of the passed Task instance.
RMI
使用
Java
对象序列化机制在
JVM
之间以值传递方式传输对象。对象要能被序列化就需要实现
java.io.Serializable
这个标识接口,因此实现
Task
接口的类也要实现
java.io.Serializable
接口,作为
Task
执行结果的对象的类也必须要实现这个接口。
RMI uses the Java object serialization mechanism to transport objects by value between Java virtual machines. For an object to be considered serializable, its class must implement the java.io.Serializable marker interface. Therefore, classes that implement the Task interface must also implement Serializable, as must the classes of objects used for task results.
不同种类的任务都能被一个
Compute
对象执行,只要这些任务都实现了
Task
接口类型。实现这个接口的类可以包含任何执行计算所需要的数据和其他执行计算所需要的方法。
Different kinds of tasks can be run by a Compute object as long as they are implementations of the Task type. The classes that implement this interface can contain any data needed for the computation of the task and any other methods needed for the computation.
这里说说
RMI
如何使得这个简单的
compute engine
成为可能。
RMI
假定
Task
对象由
Java
语言程序编写,
compute engine
先前不知道的
Task
对象的实现,在需要时由
RMI
下载到
compute engine
所在的
JVM
。这个能力使得
Compute engine
的
Client
能够定义新的将要在
Server
上运行的任务,而不需要代码显式的被安装在
Server
机器上。
Here is how RMI makes this simple compute engine possible. Because RMI can assume that the Task objects are written in the Java programming language, implementations of the Task object that were previously unknown to the compute engine are downloaded by RMI into the compute engine's Java virtual machine as needed. This capability enables clients of the compute engine to define new kinds of tasks to be run on the server machine without needing the code to be explicitly installed on that machine.
由
ComputeEngine
类实现的
compute engine,
实现了
Compute
接口,使得不同种类的任务能够通过调用它提供的
executeTask
方法提交到服务端执行。这些任务被运行实现的
execute
方法,
返回结果到远程的
client
。
The compute engine, implemented by the ComputeEngine class, implements the Compute interface, enabling different tasks to be submitted to it by calls to its executeTask method. These tasks are run using the task's implementation of the execute method and the results, are returned to the remote client.
实现 Remote Interface ( Implementing a Remote Interface )
这一节讨论实现
compute engine
类的任务。概括的说,一个实现远程接口的类至少需要做以下步骤:
-
声明要实现的
remote interface
-
为每个
remote
对象的定义构造函数
-
实现
remote interface
里的远程调用方法
This section discusses the task of implementing a class for the compute engine. In general, a class that implements a remote interface should at least do the following:
-
Declare the remote interfaces being implemented
-
Define the constructor for each remote object
-
Provide an implementation for each remote method in the remote interfaces
一个
RMI
的
server
端程序需要创建初始的远程对象并把他们发布到
RMI
的环境,使其能够接受远程调用。这一步骤可以包括在远程对象某个方法中,也可以在其他类的实体对象中。这一步骤要做如下步骤:
-
创建并安装一个
security manager
-
创建并发布一个或者多个远程对象
-
使用
RMI registry
注册至少一个远程对象
An RMI server program needs to create the initial remote objects and export them to the RMI runtime, which makes them available to receive incoming remote invocations. This setup procedure can be either encapsulated in a method of the remote object implementation class itself or included in another class entirely. The setup procedure should do the following:
-
Create and install a security manager
-
Create and export one or more remote objects
-
Register at least one remote object with the RMI registry (or with another naming service, such as a service accessible through the Java Naming and Directory Interface) for bootstrapping purposes
下面是
ComputeEngine
的全部实现。这个类实现了远程调用接口(
remote interface
)
Compute
,还有
main
方法,用来安装
compute engine
。下面是这个类的源代码
:
The complete implementation of the compute engine follows. The engine.ComputeEngine class implements the remote interface Compute and also includes the main method for setting up the compute engine. Here is the source code for the ComputeEngine class:
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import compute.Compute;
import compute.Task;
public class ComputeEngine implements Compute {
public ComputeEngine() {
super ();
}
public < T > T executeTask(Task < T > t) {
return t.execute();
}
public static void main(String[] args) {
if (System.getSecurityManager() == null ) {
System.setSecurityManager( new SecurityManager());
}
try {
String name = " Compute " ;
Compute engine = new ComputeEngine();
Compute stub =
(Compute) UnicastRemoteObject.exportObject(engine, 0 );
Registry registry = LocateRegistry.getRegistry();
registry.rebind(name, stub);
System.out.println( " ComputeEngine bound " );
} catch (Exception e) {
System.err.println( " ComputeEngine exception: " );
e.printStackTrace();
}
}
}
下面的部分讨论
compute engine
实现的所有组成部分
.
The following sections discuss each component of the compute engine implementation.
声明要实现的远程接口( Declaring the Remote Interfaces Being Implemented )
Compute engine
类的声明如下
:
The implementation class for the compute engine is declared as follows:
这个声明语句说明的意思是:这个类实现
Compute
远程接口,所有能当作远程对象来使用
ComputeEngine
类实现了一个远程接口,除此之外还包含两个可以被本地调用的方法,第一个是构造函数,第二个是
main
方法,用来创建
ComputeEngine
的实例并发布使其能被
client
使用。
This declaration states that the class implements the Compute remote interface and therefore can be used for a remote object.
The ComputeEngine class defines a remote object implementation class that implements a single remote interface and no other interfaces. The ComputeEngine class also contains two executable program elements that can only be invoked locally. The first of these elements is a constructor for ComputeEngine instances. The second of these elements is a main method that is used to create a ComputeEngine instance and make it available to clients.
定义远程对象的构造函数( Defining the Constructor for the Remote Object )
ComputeEngine
类只有一个不带参数的构造函数,源码如下:
The ComputeEngine class has a single constructor that takes no arguments. The code for the constructor is as follows:
super ();
}
ComputeEngine类的构造函数调用超类的构造函数,这里也就是
Object
类的无参构造函数。就算我们这里省略不写,超类的构造函数还是会被调用的,为了尽量清楚,我们还是写出来了。
This constructor just invokes the superclass constructor, which is the no-argument constructor of the Object class. Although the superclass constructor gets invoked even if omitted from the ComputeEngine constructor, it is included for clarity.
提供每个远程方法的实现( Providing Implementations for Each Remote Method )
远程对象类实现了每个远程接口里的可被远程调用的方法。
Compute
接口只有一个远程方法,
executeTask
,实现如下:
The class for a remote object provides implementations for each remote method specified in the remote interfaces. The Compute interface contains a single remote method, executeTask, which is implemented as follows:
return t.execute();
}
这个方法实现了
ComputeEngine
这个远程对象和他的
Clients
之间的协议。每个
Client
给
ComputeEngine
一个
Task
对象,
Task
对象实现
Task
接口里面的
execute
方法。
ComputeEngine
执行每个
Client
的
task
,直接返回执行结果给
Client
。
This method implements the protocol between the ComputeEngine remote object and its clients. Each client provides the ComputeEngine with a Task object that has a particular implementation of the Task interface's execute method. The ComputeEngine executes each client's task and returns the result of the task's execute method directly to the client.
在 RMI 中传递对象( Passing Objects in RMI )
远程方法需要的参数或者是返回值几乎可以是任何对象,包括本地对象,远程对象,元数据类型。更确切的说,任何实体对象只要是符合如下类型的实例都能作为远程对象可用的参数或者返回值,这些类型包括元数据类型,远程对象或者是一个可序列化对象(实现了
java.io.Serializable
的对象)。
Arguments to or return values from remote methods can be of almost any type, including local objects, remote objects, and primitive data types. More precisely, any entity of any type can be passed to or from a remote method as long as the entity is an instance of a type that is a primitive data type, a remote object, or a serializable object, which means that it implements the interface java.io.Serializable.
有些对象类型不能满足上面的要求,因此不能传递给远程方法作为参数,亦或作为远程方法的返回值。这些对象如线程或者是文件描述符,它们只在单个地址空间内是有意义的,许多核心的类都实现了
Serializable
接口,这主要包括
java.lang
和
java.util
包下面的类。
Some object types do not meet any of these criteria and thus cannot be passed to or returned from a remote method. Most of these objects, such as threads or file descriptors, encapsulate information that makes sense only within a single address space. Many of the core classes, including the classes in the packages java.lang and java.util, implement the Serializable interface.
如何传递参数和获得返回值遵循如下的约定:
-
远程对象本质上通过引用传递。一个远程对象的引用可以理解为一个存根,这个存根作为一个实现了所有远程接口的客户端的代理。
-
本地对象通过值拷贝传递,其中使用对象序列化的技术。默认的拷贝方法是,拷贝除了被标识为
static
和
transient
的所有的域。默认的序列化行为可以被重载。
The rules governing how arguments and return values are passed are as follows:
-
Remote objects are essentially passed by reference. A remote object reference is a stub, which is a client-side proxy that implements the complete set of remote interfaces that the remote object implements.
-
Local objects are passed by copy, using object serialization. By default, all fields are copied except fields that are marked static or transient. Default serialization behavior can be overridden on a class-by-class basis.
通过引用传递远程对象意味着所有对这个对象状态的修改都会影响到原来的远程对象。当传递远程对象引用的时候,只有远程接口提供的方法才能被接收者使用。任何在实现类中定义的方法或者类实现的非远程接口中定义的方法,接收端是不可用的。
Passing a remote object by reference means that any changes made to the state of the object by remote method invocations are reflected in the original remote object. When a remote object is passed, only those interfaces that are remote interfaces are available to the receiver. Any methods defined in the implementation class or defined in non-remote interfaces implemented by the class are not available to that receiver.
举例来说,如果你要传递指向
ComputeEngine
的实例的引用,那么接收端只能访问到
Compute engine
的
executeTask
方法,接受端看不到
ComputeEngine
的构造函数,
main
方法,或者这它实现的任何
java.lang.Object
的方法。
For example, if you were to pass a reference to an instance of the ComputeEngine class, the receiver would have access only to the compute engine's executeTask method. That receiver would not see the ComputeEngine constructor, its main method, or its implementation of any methods of java.lang.Object.
在远程方法调用的参数返回值中,非远程对象的对象通过值拷贝传递。因此,在接受端的
JVM
中一个对象的拷贝被创建。任何对对象状态的更改只会影响到拷贝的对象,而不会影响发送端的原始对象实例。任何由发送端对对象状态的更改,只会影响发送端原始对象的实例,而不会影响接收端该对象的拷贝对象。
In the parameters and return values of remote method invocations, objects that are not remote objects are passed by value. Thus, a copy of the object is created in the receiving Java virtual machine. Any changes to the object's state by the receiver are reflected only in the receiver's copy, not in the sender's original instance. Any changes to the object's state by the sender are reflected only in the sender's original instance, not in the receiver's copy.
实现 Server 的 main
方法( Implementing the Server's main Method )
ComputeEngine
中最复杂的代码就是这个
main
方法。
Main
方法用来启动
ComputeEngien
因此需要做必要的初始化,为
server
接受
client
的请求做准备。这个方法不是远程方法,就是说它不能被另一个
JVM
调用。因为
main
方法声明成静态的,这个方法不会和一个对象关联,只和
ComputeEngine
类关联。
The most complex method of the ComputeEngine implementation is the main method. The main method is used to start the ComputeEngine and therefore needs to do the necessary initialization and housekeeping to prepare the server to accept calls from clients. This method is not a remote method, which means that it cannot be invoked from a different Java virtual machine. Because the main method is declared static, the method is not associated with an object at all but rather with the class ComputeEngine.
创建安装一个 Security Manager ( Creating and Installing a Security Manager )
main
方法的第一个任务是创建安装一个安全管理器,保护来自未被信任的下载到的代码对系统资源的访问。安全管理器决定了下载到的代码,是否有权限访问本地文件系统,或者执行有特殊权限的操作。
The main method's first task is to create and install a security manager, which protects access to system resources from untrusted downloaded code running within the Java virtual machine. A security manager determines whether downloaded code has access to the local file system or can perform any other privileged operations.
如果
RMI
程序没有安装安全管理器,
RMI
将不会为作为参数接受的对象或者作为远程方法调用返回值的对象下载类。这个限制使得由下载代码执行的操作必须符合安全策略。
If an RMI program does not install a security manager, RMI will not download classes (other than from the local class path) for objects received as arguments or return values of remote method invocations. This restriction ensures that the operations performed by downloaded code are subject to a security policy.
下面是创建安装安全管理器的代码:
Here's the code that creates and installs a security manager:
if (System.getSecurityManager() == null) { System.setSecurityManager(new SecurityManager()); }
使得远程对象对 Clients 可用( Making the Remote Object Available to Clients )
下一步,main方法创建ComputeEngine的实例,然后使用下面的语句导出到RMI运行时环境。
Next, the main method creates an instance of ComputeEngine and exports it to the RMI runtime with the following statements:
Compute engine = new ComputeEngine();
Compute stub =
(Compute) UnicastRemoteObject.exportObject(engine, 0);
静态方法
UnicastRemoteObject.exportObject
导出提供的远程对象,从而它可以接受来自远程
client
的远程方法调用。第二个参数,是一个整型值,指定了使用哪个
TCP
端口监听远程调用请求。通常用
0
,指的是使用匿名的端口。实际端口在运行时由
RMI
指定,或者是由操作系统指定。然后也可以指定非零的值作为监听端口。一旦
exportObject
方法调用成功返回,
ComputeEngine
远程对象就准备好处理远程调用了。
The static UnicastRemoteObject.exportObject
method exports the supplied remote object so that it can receive invocations of its remote methods from remote clients. The second argument, an int
, specifies which TCP port to use to listen for incoming remote invocation requests for the object. It is common to use the value zero, which specifies the use of an anonymous port. The actual port will then be chosen at runtime by RMI or the underlying operating system. However, a non-zero value can also be used to specify a specific port to use for listening. Once the exportObject
invocation has returned successfully, the ComputeEngine
remote object is ready to process incoming remote invocations.
exportObject
方法返回一个导出的远程对象的
stub
。注意
stub
的类型必须是接口
Compute
,而不是
ComputeEngine
,因为远程对象的
stub
仅仅实现了导出的远程对象实现的远程接口。
The exportObject
method returns a stub for the exported remote object. Note that the type of the variable stub
must be Compute
, not ComputeEngine
, because the stub for a remote object only implements the remote interfaces that the exported remote object implements.
exportObject
方法声明抛出
RemoteException
,这是一个受检查的异常类型。
main
方法在
try/catch
块中处理这个异常。如何这个异常没有这样处理,那么就必须在
main
方法的声明中抛出这个异常子句。在必要的通讯资源不可用,例如需要的端口被占用,尝试导出远程对象这个操作就会抛出这个异常。
The exportObject
method declares that it can throw a RemoteException
, which is a checked exception type. The main
method handles this exception with its try
/ catch
block. If the exception were not handled in this way, RemoteException
would have to be declared in the throws
clause of the main
method. An attempt to export a remote object can throw a RemoteException
if the necessary communication resources are not available, such as if the requested port is bound for some other purpose.
因为
client
要调用远程对象的方法,所以它必须首先获得远程对象的引用。获取远程对象的引用像程序里获取任何其他对象的引用一样简单,诸如作为方法返回值获得对象的应用,或者是返回某个含有对象引用的数据结构。
Before a client can invoke a method on a remote object, it must first obtain a reference to the remote object. Obtaining a reference can be done in the same way that any other object reference is obtained in a program, such as by getting the reference as part of the return value of a method or as part of a data structure that contains such a reference.
RMI
系统为远程对象提供了一个特殊的类型,
RMI
注册,其目的是为了获得其他远程对象的引用。
RMI
注册机制是一个简单的远程对象命名服务,使得
client
能够通过远程对象的名字获得远程对象的引用。注册作为代表性的仅被用作定位
client
需要使用的远程对象。第一个远程对象可能需要提供找到其他对象的支持。
The system provides a particular type of remote object, the RMI registry, for finding references to other remote objects. The RMI registry is a simple remote object naming service that enables clients to obtain a reference to a remote object by name. The registry is typically only used to locate the first remote object that an RMI client needs to use. That first remote object might then provide support for finding other objects.
java.rmi.registry.Registry
远程接口是用来在注册处绑定(或者注册)和查找远程对象的
API
。
java.rmi.registry.LocateRegistry
累提供了将远程对象引用和也都网络地址(主机和端口)合成的静态方法。这些方法创建远程引用对象包含了指定的网络地址,而不需要执行任何的远程通信。
LocateRegistry
类还提供了在当前
JVM
中创建新注册的方法,尽管我们这个教程没有使用到那些方法。一旦一个远程对象使用
RMI
在本地主机注册了,任何其他主机上的
clients
能够通过远程对象的名字查找远程对象,获得远程对象的引用,然后调用远程方法。这个注册表能够被所以运行在一个主机上的所以
server
共享,或者是每个
server
可以创建使用自己的注册表。
The java.rmi.registry.Registry
remote interface is the API for binding (or registering) and looking up remote objects in the registry. The java.rmi.registry.LocateRegistry
class provides static methods for synthesizing a remote reference to a registry at a particular network address (host and port). These methods create the remote reference object containing the specified network address without performing any remote communication. LocateRegistry
also provides static methods for creating a new registry in the current Java virtual machine, although this example does not use those methods. Once a remote object is registered with an RMI registry on the local host, clients on any host can look up the remote object by name, obtain its reference, and then invoke remote methods on the object. The registry can be shared by all servers running on a host, or an individual server process can create and use its own registry.
ComputeEngine
类用如下代码为对象创建名称:
The ComputeEngine
class creates a name for the object with the following statement:
String name = "Compute";
下一步将名字添加到
server
运行的
RMI
注册表中,代码如下
:
The code then adds the name to the RMI registry running on the server. This step is done later with the following statements:
Registry registry = LocateRegistry.getRegistry();
registry.rebind(name, stub);
rebind
调用产生一个对本机
RMI
注册表的远程调用。像任何远程调用一样,这个调用可能抛出
RemoteException
,这个异常将被
main
方法的
catch
语句块处理。
This rebind
invocation makes a remote call to the RMI registry on the local host. Like any remote call, this call can result in a RemoteException
being thrown, which is handled by the catch
block at the end of the main
method.
关于
the Registry.rebind
调用要注意如下几点:
·
无参数的
LocateRegistry.getRegistry
方法返回本地主机在默认注册表端口
1099
上对远程对象
Registry
的引用。如何注册表不是在默认端口上被创建的,你需要使用带一个整形参数的重载方法。
·
当
RMI
远程对象注册表上远程调用产生的时候,远程对象的
stub
被传递,而不是远程对象本身的拷贝。远程对象的实现,诸如
ComputeEngine
的实例,永远不会离开它被创建的那个
JVM
。因此,当
client
在
server
的远程对象注册表中执行查找的时候,
stub
的拷贝被返回。远程对象在这样的情况下是通过传递引用高效运作的,而不是值传递。
·
由于安全原因,一个应用程序只能在运行于同一个主机上的注册表上
bind
,
unbind
或者
rebind
远程对象的引用。这个限制阻止了远程
client
删除或者重写
server
远程对象注册表中的实体。然而我们能够在任何主机上做出这个
lookup
请求,本地或者远程都可以。
Note the following about the Registry.rebind
invocation:
- The no-argument overload of
LocateRegistry.getRegistry
synthesizes a reference to a registry on the local host and on the default registry port, 1099. You must use an overload that has anint
parameter if the registry is created on a port other than 1099. - When a remote invocation on the registry is made, a stub for the remote object is passed instead of a copy of the remote object itself. Remote implementation objects, such as instances of
ComputeEngine
, never leave the Java virtual machine in which they were created. Thus, when a client performs a lookup in a server's remote object registry, a copy of the stub is returned. Remote objects in such cases are thus effectively passed by (remote) reference rather than by value. - For security reasons, an application can only
bind
,unbind
, orrebind
remote object references with a registry running on the same host. This restriction prevents a remote client from removing or overwriting any of the entries in a server's registry. Alookup
, however, can be requested from any host, local or remote.
Server
一向本地
RMI
注册表注册,它就会打印一条信息,表明它准备好开始处理远程调用了。然后
main
方法完成了。没有必要用一个线程让
server
一直处于活跃状态。只要另外一个
JVM
中还有对
ComputeEngine
对象的引用,不管是本地的还是远程的,
ComputeEngine
对象就不会被关闭或者被垃圾回收。因为程序在注册表里绑定了
ComputeEngine
对象的引用,远程的
client
和注册表都是可以访问这个对象引用的。
RMI
系统让
ComputeEngine
的进程处于运行状态。
ComputeEngine
一直是可用的不会被回收,直到它的绑定被从注册表中删除,并且没有远程
client
持有
ComputeEngine
对象的引用。
Once the server has registered with the local RMI registry, it prints a message indicating that it is ready to start handling calls. Then, the main
method completes. It is not necessary to have a thread wait to keep the server alive. As long as there is a reference to the ComputeEngine
object in another Java virtual machine, local or remote, the ComputeEngine
object will not be shut down or garbage collected. Because the program binds a reference to the ComputeEngine
in the registry, it is reachable from a remote client, the registry itself. The RMI system keeps the ComputeEngine
's process running. The ComputeEngine
is available to accept calls and won't be reclaimed until its binding is removed from the registry and no remote clients hold a remote reference to the ComputeEngine
object.
ComputeEngine.main
方法的最后一段代码处理可能抛出的异常。代码可能抛出的唯一受检查的异常类型是
RemoteException
,可能是
UnicastRemoteObject.exportObject
调用抛出此异常,也可能是
rebind
方法。这两种情况下,程序仅打印错误消息然后退出。在某些分布式应用中,从错误中恢复使得远程调用仍然可用。举例来说,应用程序尝试重试这个操作,或者选择另外的
server
继续这个操作。
The final piece of code in the ComputeEngine.main
method handles any exception that might arise. The only checked exception type that could be thrown in the code is RemoteException
, either by the invocation or by the registry rebind
invocation. In either case, the program cannot do much more than exit after printing an error message. In some distributed applications, recovering from the failure to make a remote invocation is possible. For example, the application could attempt to retry the operation or choose another server to continue the operation.
创建客户端程序( Creating a Client Program )
Compute engine
是一个相对简单的程序:它运行交给它的任务。
Compute engine
的
client
程序更复杂一点。一个
client
需要访问
compute engine
,还需要定义
compute engine
要执行的任务。
The compute engine is a relatively simple program: it runs tasks that are handed to it. The clients for the compute engine are more complex. A client needs to call the compute engine, but it also has to define the task to be performed by the compute engine.
我们例子中的
client
程序由两个单独的类组成。第一个类,
ComputePi
,查找调用一个
Compute
对象。第二个类,
Pi
,实现了
Task
接口,定义了
compute engine
的任务。
Pi
这个类的任务就是计算
值到若干小数位数。
Two separate classes make up the client in our example. The first class, ComputePi
, looks up and invokes a Compute
object. The second class, Pi
, implements the Task
interface and defines the work to be done by the compute engine. The job of the Pi
class is to compute the value of to some number of decimal places.
非远程的
Task
接口定义如下:
The non-remote Task
interface is defined as follows:
return t.execute();
}
调用
Compute
对象的代码需要获得这个对象的引用,创建一个
Task
对象,然后请求任务被执行。我们将在后面给出任务类
Pi
的定义。一个
Pi
对象有一个构造参数,这个构造参数也就是期望的计算
结果的精确度。任务执行的结果是
java.math.BigDecimal
的返回值,表示
计算出的到指定精确度的值。
The code that invokes a Compute
object's methods must obtain a reference to that object, create a Task
object, and then request that the task be executed. The definition of the task class Pi
is shown later. A Pi
object is constructed with a single argument, the desired precision of the result. The result of the task execution is a java.math.BigDecimal
representing calculated to the specified precision.
这里给出
client.ComputePi
的源代码,即主要的
client
类:
Here is the source code for client.ComputePi
, the main client class:
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.math.BigDecimal;
import compute.Compute;
public class ComputePi {
public static void main(String args[]) {
if (System.getSecurityManager() == null ) {
System.setSecurityManager( new SecurityManager());
}
try {
String name = " Compute " ;
Registry registry = LocateRegistry.getRegistry(args[ 0 ]);
Compute comp = (Compute) registry.lookup(name);
Pi task = new Pi(Integer.parseInt(args[ 1 ]));
BigDecimal pi = comp.executeTask(task);
System.out.println(pi);
} catch (Exception e) {
System.err.println( " ComputePi exception: " );
e.printStackTrace();
}
}
}
像
ComputeEngine
服务端程序一样,客户端程序从安装安全管理器开始。这一步是必要的,因为接收
server
远程对象
stub
的过程需要从
server
下载类定义。为了
RMI
能够下载类,安全管理器必须是有效的。
Like the ComputeEngine
server, the client begins by installing a security manager. This step is necessary because the process of receiving the server remote object's stub could require downloading class definitions from the server. For RMI to download classes, a security manager must be in force.
安装完
RMI
安全管理器之后,
client
创建一个用来查找
Compute
远程对象的名字,这个名字也就是服务端用来绑定
ComputeEngine
远程对象的名字。客户端也是用
LocateRegistry.getRegistry API
来获得
server
主机远程对象注册表的引用的。命令行参数的第一个值是运行
Compute
对象的远程主机的名字
@
。
client
然后调用
registry
的
lookup
方法在
server
的远程对象注册表中查找指定名称的远程对象。
LocateRegistry.getRegistry
方法的特殊重载方法这里被用到了,这个方法接受一个字符串参数,返回指定主机上默认端口
1099
上远程对象注册表对象的引用。如何远程对象注册表不是在默认端口
1099
上被创建的,你需要使用带一个整形参数的重载方法。
After installing a security manager, the client constructs a name to use to look up a Compute
remote object, using the same name used by ComputeEngine
to bind its remote object. Also, the client uses the LocateRegistry.getRegistry
API to synthesize a remote reference to the registry on the server's host. The value of the first command-line argument, args[0]
, is the name of the remote host on which the Compute
object runs. The client then invokes the lookup
method on the registry to look up the remote object by name in the server host's registry. The particular overload of LocateRegistry.getRegistry
used, which has a single String
parameter, returns a reference to a registry at the named host and the default registry port, 1099. You must use an overload that has an int
parameter if the registry is created on a port other than 1099.
下一步
client
创建一个新的
Pi
对象,将命令行参数的第二个值作为整形值传递给这个类的构造函数。这个参数指出了计算的精确度数。然后
client
调用
Compute
远程对象的
executeTask
方法返回一个
BigDecimal
对象,程序中用变量
result
存储这个值。最后程序打印出这个值。下图描述了
ComputePi client
,
rmiregistry
,
ComputeEngine
之间的消息流。
Next, the client creates a new Pi
object, passing to the Pi
constructor the value of the second command-line argument, args[1]
, parsed as an integer. This argument indicates the number of decimal places to use in the calculation. Finally, the client invokes the executeTask
method of the Compute
remote object. The object passed into the executeTask
invocation returns an object of type BigDecimal
, which the program stores in the variable result
. Finally, the program prints the result. The following figure depicts the flow of messages among the ComputePi
client, the rmiregistry
, and the ComputeEngine
.
Pi
类实现了
Task
接口,计算
值到指定的小数位。在这个例子中实际的算法不是重点。重要的是算法计算代价是昂贵的,也就是说你也许会希望它在一个有此计算能力的
Server
上执行。
The Pi
class implements the Task
interface and computes the value of to a specified number of decimal places. For this example, the actual algorithm is unimportant. What is important is that the algorithm is computationally expensive, meaning that you would want to have it executed on a capable server.
下面是实现了
Task
接口的
client.Pi
类的源代码:
Here is the source code for client.Pi
, the class that implements the Task
interface:
import compute.Task;
import java.io.Serializable;
import java.math.BigDecimal;
public class Pi implements Task < BigDecimal > , Serializable {
private static final long serialVersionUID = 227L ;
/** */ /** constants used in pi computation */
private static final BigDecimal FOUR =
BigDecimal.valueOf( 4 );
/** */ /** rounding mode to use during pi computation */
private static final int roundingMode =
BigDecimal.ROUND_HALF_EVEN;
/** */ /** digits of precision after the decimal point */
private final int digits;
/** */ /**
* Construct a task to calculate pi to the specified
* precision.
*/
public Pi( int digits) {
this .digits = digits;
}
/** */ /**
* Calculate pi.
*/
public BigDecimal execute() {
return computePi(digits);
}
/** */ /**
* Compute the value of pi to the specified number of
* digits after the decimal point. The value is
* computed using Machin's formula:
*
* pi/4 = 4*arctan(1/5) - arctan(1/239)
*
* and a power series expansion of arctan(x) to
* sufficient precision.
*/
public static BigDecimal computePi( int digits) {
int scale = digits + 5 ;
BigDecimal arctan1_5 = arctan( 5 , scale);
BigDecimal arctan1_239 = arctan( 239 , scale);
BigDecimal pi = arctan1_5.multiply(FOUR).subtract(
arctan1_239).multiply(FOUR);
return pi.setScale(digits,
BigDecimal.ROUND_HALF_UP);
}
/** */ /**
* Compute the value, in radians, of the arctangent of
* the inverse of the supplied integer to the specified
* number of digits after the decimal point. The value
* is computed using the power series expansion for the
* arc tangent:
*
* arctan(x) = x - (x^3)/3 + (x^5)/5 - (x^7)/7 +
* (x^9)/9
*/
public static BigDecimal arctan( int inverseX,
int scale)
{
BigDecimal result, numer, term;
BigDecimal invX = BigDecimal.valueOf(inverseX);
BigDecimal invX2 =
BigDecimal.valueOf(inverseX * inverseX);
numer = BigDecimal.ONE.divide(invX,
scale, roundingMode);
result = numer;
int i = 1 ;
do {
numer =
numer.divide(invX2, scale, roundingMode);
int denom = 2 * i + 1 ;
term =
numer.divide(BigDecimal.valueOf(denom),
scale, roundingMode);
if ((i % 2 ) != 0 ) {
result = result.subtract(term);
} else {
result = result.add(term);
}
i ++ ;
} while (term.compareTo(BigDecimal.ZERO) != 0 );
return result;
}
}
注意所有的可序列化的类,不管他们直接或者间接实现了
Serializable
接口,都必须声明一个
private staitic final
的叫做
serialVersionUID
的域来保证不同版本之间序列化的兼容性。如果这个类之前没有发布过版本,那么这个域的值可以是任何长整行的值,就像程序里
Pi
类使用的
227L
,只要将来的版本中也使用这个值就可以了。如果这个类的之前版本没有显式声明
serialVersionUID
,但是于那个版本的兼容性非常重要,那么新版本的这个类必须显式的使用那个先前版本默认的隐式的
serialVersionUID
的值。
Serialver
工具能够在先前的版本上运行以得出默认的隐式的
serialVersionUID
值。
Note that all serializable classes, whether they implement the Serializable
interface directly or indirectly, must declare a private
static
final
field named serialVersionUID
to guarantee serialization compatibility between versions. If no previous version of the class has been released, then the value of this field can be any long
value, similar to the 227L
used by Pi
, as long as the value is used consistently in future versions. If a previous version of the class has been released without an explicit serialVersionUID
declaration, but serialization compatibility with that version is important, then the default implicitly computed value for the previous version must be used for the value of the new version's explicit declaration. The serialver
tool can be run against the previous version to determine the default computed value for it.
这个例子最有意思的的特性是
Compute
类的实现对象不需要
Pi
的类定义,直到
Pi
对象作为一个参数传递到
Compute
对象的
executeTask
方法。那个时候,
Pi
的类定义才会被
RMI
装载到
Compute
对象所在的
JVM
,然后
excute
方法被调用,任务代码被执行。
Pi
任务的计算结果
BigDecimal
对象,被传回调用
Compute
对象的
client
,然后被用来作为结果打印出来。
The most interesting feature of this example is that the Compute
implementation object never needs the Pi
class's definition until a Pi
object is passed in as an argument to the executeTask
method. At that point, the code for the class is loaded by RMI into the Compute
object's Java virtual machine, the execute
method is invoked, and the task's code is executed. The result, which in the case of the Pi
task is a BigDecimal
object, is handed back to the calling client, where it is used to print the result of the computation.
事实上
Task
对象计算
Pi
的值和
ComputeEngine
对象是没有关系的。举例来说你也可以实现一个产生随机素数的随机算法。任务也可以是计算加强型的,这种任务也是传递给
ComputeEngine
的一个好的选择,但是这份代码就要截然不同了。当任务对象传递到
Compute
对象的时候,这份代码也能被下载。和在需要时计算
的代码才被引入的情况类似,产生随机数的代码在需要时也才会被引进。
Compute
对象仅知道它接收的每个对象都实现了
execute
方法。
Compute
对象不知道也不需要知道这个实现是怎么样的。
The fact that the supplied Task
object computes the value of Pi
is irrelevant to the ComputeEngine
object. You could also implement a task that, for example, generates a random prime number by using a probabilistic algorithm. That task would also be computationally intensive and therefore a good candidate for passing to the ComputeEngine
, but it would require very different code. This code could also be downloaded when the Task
object is passed to a Compute
object. In just the way that the algorithm for computing is brought in when needed, the code that generates the random prime number would be brought in when needed. The Compute
object knows only that each object it receives implements the execute
method. The Compute
object does not know, and does not need to know, what the implementation does.
编译运行范例( Compiling and Running the Example )
Now that the code for the compute engine example has been written, it needs to be compiled and run.
Compiling the Example Programs
In this section, you learn how to compile the server and the client programs that make up the compute engine example.
Running the Example Programs
Finally, you run the server and client programs and consequently compute the value of .
编译示例程序( Compiling the Example Programs )
In a real-world scenario in which a service such as the compute engine is deployed, a developer would likely create a Java Archive (JAR) file that contains the Compute
and Task
interfaces for server classes to implement and client programs to use. Next, a developer, perhaps the same developer of the interface JAR file, would write an implementation of the Compute
interface and deploy that service on a machine available to clients. Developers of client programs can use the Compute
and the Task
interfaces, contained in the JAR file, and independently develop a task and client program that uses a Compute
service.
In this section, you learn how to set up the JAR file, server classes, and client classes. You will see that the client's Pi
class will be downloaded to the server at runtime. Also, the Compute
and Task
interfaces will be downloaded from the server to the registry at runtime.
This example separates the interfaces, remote object implementation, and client code into three packages:
-
compute
–Compute
andTask
interfaces -
engine
–ComputeEngine
implementation class -
client
–ComputePi
client code andPi
task implementation
First, you need to build the interface JAR file to provide to server and client developers.
Building a JAR File of Interface Classes
First, you need to compile the interface source files in the compute
package and then build a JAR file that contains their class files. Assume that user waldo
has written these interfaces and placed the source files in the directory c:\home\waldo\src\compute
on Windows or the directory /home/waldo/src/compute
on Solaris OS or Linux. Given these paths, you can use the following commands to compile the interfaces and create the JAR file:
Microsoft Windows :
cd c:\home\waldo\src javac compute\Compute.java compute\Task.java jar cvf compute.jar compute\*.class
Solaris OS or Linux :
cd /home/waldo/src javac compute/Compute.java compute/Task.java jar cvf compute.jar compute/*.class
The jar
command displays the following output due to the -v
option:
added manifest : compute/Compute.class(in = 307) (out= 201)(deflated 34%) adding: compute/Task.class(in = 217) (out= 149)(deflated 31%)
Now, you can distribute the compute.jar
file to developers of server and client applications so that they can make use of the interfaces.
After you build either server-side or client-side classes with the javac
compiler, if any of those classes will need to be dynamically downloaded by other Java virtual machines, you must ensure that their class files are placed in a network-accessible location. In this example, for Solaris OS or Linux this location is /home/user/public_html/classes
because many web servers allow the accessing of a user's public_html
directory through an HTTP URL constructed as http://host/~user/
. If your web server does not support this convention, you could use a different location in the web server's hierarchy, or you could use a file URL instead. The file URLs take the form file:/home/user/public_html/classes/
on Solaris OS or Linux and the form file:/c:/home/user/public_html/classes/
on Windows. You may also select another type of URL, as appropriate.
The network accessibility of the class files enables the RMI runtime to download code when needed. Rather than defining its own protocol for code downloading, RMI uses URL protocols supported by the Java platform (for example, HTTP) to download code. Note that using a full, heavyweight web server to serve these class files is unnecessary. For example, a simple HTTP server that provides the functionality needed to make classes available for downloading in RMI through HTTP can be found at http://java.sun.com/javase/technologies/core/basic/rmi/class-server.zip.
Building the Server Classes
The engine
package contains only one server-side implementation class, ComputeEngine
, the implementation of the remote interface Compute
.
Assume that user ann
, the developer of the ComputeEngine
class, has placed ComputeEngine.java
in the directory c:\home\ann\src\engine
on Windows or the directory /home/ann/src/engine
on Solaris OS or Linux. She is deploying the class files for clients to download in a subdirectory of her public_html
directory, c:\home\ann\public_html\classes
on Windows or /home/ann/public_html/classes
on Solaris OS or Linux. This location is accessible through some web servers as http://host:port/~ann/classes/
.
The ComputeEngine
class depends on the Compute
and Task
interfaces, which are contained in the compute.jar
JAR file. Therefore, you need the compute.jar
file in your class path when you build the server classes. Assume that the compute.jar
file is located in the directory c:\home\ann\public_html\classes
on Windows or the directory /home/ann/public_html/classes
on Solaris OS or Linux. Given these paths, you can use the following commands to build the server classes:
Microsoft Windows :
cd c:\home\ann\src javac -cp c:\home\ann\public_html\classes\compute.jar engine\ComputeEngine.java
Solaris OS or Linux :
cd /home/ann/src javac -cp /home/ann/public_html/classes/compute.jar engine/ComputeEngine.java
The stub class for ComputeEngine
implements the Compute
interface, which refers to the Task
interface. So, the class definitions for those two interfaces need to be network-accessible for the stub to be received by other Java virtual machines such as the registry's Java virtual machine. The client Java virtual machine will already have these interfaces in its class path, so it does not actually need to download their definitions. The compute.jar
file under the public_html
directory can serve this purpose.
Now, the compute engine is ready to deploy. You could do that now, or you could wait until after you have built the client.
Building the Client Classes
The client
package contains two classes, ComputePi
, the main client program, and Pi
, the client's implementation of the Task
interface.
Assume that user jones
, the developer of the client classes, has placed ComputePi.java
and Pi.java
in the directory c:\home\jones\src\client
on Windows or the directory /home/jones/src/client
on Solaris OS or Linux. He is deploying the class files for the compute engine to download in a subdirectory of his public_html
directory, c:\home\jones\public_html\classes
on Windows or /home/jones/public_html/classes
on Solaris OS or Linux. This location is accessible through some web servers as http://host:port/~jones/classes/
.
The client classes depend on the Compute
and Task
interfaces, which are contained in the compute.jar
JAR file. Therefore, you need the compute.jar
file in your class path when you build the client classes. Assume that the compute.jar
file is located in the directory c:\home\jones\public_html\classes
on Windows or the directory /home/jones/public_html/classes
on Solaris OS or Linux. Given these paths, you can use the following commands to build the client classes:
Microsoft Windows :
cd c:\home\jones\src javac -cp c:\home\jones\public_html\classes\compute.jar client\ComputePi.java client\Pi.java mkdir c:\home\jones\public_html\classes\client cp client\Pi.class c:\home\jones\public_html\classes\client
Solaris OS or Linux :
cd /home/jones/src javac -cp /home/jones/public_html/classes/compute.jar client/ComputePi.java client/Pi.java mkdir /home/jones/public_html/classes/client cp client/Pi.class /home/jones/public_html/classes/client
Only the Pi
class needs to be placed in the directory public_html\classes\client
because only the Pi
class needs to be available for downloading to the compute engine's Java virtual machine. Now, you can run the server and then the client.
运行示例程序( Running the Example Programs )
A Note About Security
The server and client programs run with a security manager installed. When you run either program, you need to specify a security policy file so that the code is granted the security permissions it needs to run. Here is an example policy file to use with the server program:
grant codeBase "file:/home/ann/src/" { permission java.security.AllPermission; };
Here is an example policy file to use with the client program:
grant codeBase "file:/home/jones/src/" { permission java.security.AllPermission; };
For both example policy files, all permissions are granted to the classes in the program's local class path, because the local application code is trusted, but no permissions are granted to code downloaded from other locations. Therefore, the compute engine server restricts the tasks that it executes (whose code is not known to be trusted and might be hostile) from performing any operations that require security permissions. The example client's Pi
task does not require any permissions to execute.
In this example, the policy file for the server program is named server.policy
, and the policy file for the client program is named client.policy
.
Starting the Server
Before starting the compute engine, you need to start the RMI registry. The RMI registry is a simple server-side bootstrap naming facility that enables remote clients to obtain a reference to an initial remote object. It can be started with the rmiregistry
command. Before you execute rmiregistry
, you must make sure that the shell or window in which you will run rmiregistry
either has no CLASSPATH
environment variable set or has a CLASSPATH
environment variable that does not include the path to any classes that you want downloaded to clients of your remote objects.
To start the registry on the server, execute the rmiregistry
command. This command produces no output and is typically run in the background. For this example, the registry is started on the host zaphod
.
Microsoft Windows (use javaw
if start
is not available):
start rmiregistry
Solaris OS or Linux :
rmiregistry &
By default, the registry runs on port 1099. To start the registry on a different port, specify the port number on the command line. Do not forget to unset your CLASSPATH
environment variable.
Microsoft Windows :
start rmiregistry 2001
Solaris OS or Linux :
rmiregistry 2001 &
Once the registry is started, you can start the server. You need to make sure that both the compute.jar
file and the remote object implementation class are in your class path. When you start the compute engine, you need to specify, using the java.rmi.server.codebase
property, where the server's classes are network accessible. In this example, the server-side classes to be made available for downloading are the Compute
and Task
interfaces, which are available in the compute.jar
file in the public_html\classes
directory of user ann
. The compute engine server is started on the host zaphod
, the same host on which the registry was started.
Microsoft 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=zaphod.east.sun.com engine.ComputeEngine
Solaris OS or Linux :
java -cp /home/ann/src:/home/ann/public_html/classes/compute.jar -Djava.rmi.server.codebase=http://zaphod/~ann/classes/compute.jar -Djava.rmi.server.hostname=zaphod.east.sun.com -Djava.security.policy=server.policy engine.ComputeEngine
The above java
command defines the following system properties:
- The
java.rmi.server.codebase
property specifies the location, a codebase URL, from which the definitions for classes originating from this server can be downloaded. If the codebase specifies a directory hierarchy (as opposed to a JAR file), you must include a trailing slash at the end of the codebase URL. - The
java.rmi.server.hostname
property specifies the host name or address to put in the stubs for remote objects exported in this Java virtual machine. This value is the host name or address used by clients when they attempt to communicate remote method invocations. By default, the RMI implementation uses the server's IP address as indicated by thejava.net.InetAddress.getLocalHost
API. However, sometimes, this address is not appropriate for all clients and a fully qualified host name would be more effective. To ensure that RMI uses a host name (or IP address) for the server that is routable from all potential clients, set thejava.rmi.server.hostname
property. - The
java.security.policy
property is used to specify the policy file that contains the permissions you intend to grant.
Starting the Client
Once the registry and the compute engine are running, you can start the client, specifying the following:
- The location where the client serves its classes (the
Pi
class) by using thejava.rmi.server.codebase
property - The
java.security.policy
property, which is used to specify the security policy file that contains the permissions you intend to grant to various pieces of code - As command-line arguments, the host name of the server (so that the client knows where to locate the
Compute
remote object) and the number of decimal places to use in the calculation
Start the client on another host (a host named ford
, for example) as follows:
Microsoft 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 zaphod.east.sun.com 45
Solaris OS or Linux :
java -cp /home/jones/src:/home/jones/public_html/classes/compute.jar -Djava.rmi.server.codebase=http://ford/~jones/classes/ -Djava.security.policy=client.policy client.ComputePi zaphod.east.sun.com 45
Note that the class path is set on the command line so that the interpreter can find the client classes and the JAR file containing the interfaces. Also note that the value of the java.rmi.server.codebase
property, which specifies a directory hierarchy, ends with a trailing slash.
After you start the client, the following output is displayed:
3.141592653589793238462643383279502884197169399
下图描述了在程序执行过程中
rmiregistry
,
ComputeEngine server
,
ComputePi client
在哪里获得类定义。
The following figure illustrates where the rmiregistry
, the ComputeEngine
server, and the ComputePi
client obtain classes during program execution.
当
ComputeEngine server
绑定它的远程对象到
rmiregistry
的时候,
registry
下载
stub
类所依赖的
Compute
接口和
Task
接口。根据启动
Server
时编码的
URL
类型,这些类或者从
ComputeEngine Server
的
web
服务器被下载,或者是从文件系统被下载。
因为
ComputePi client
在其类路径里有
Compute
和
Task
接口,所以它从自己的类路径装载这两个定义,而不是
server
的编码。
最后,当
Pi
对象传递到
ComputeEngine
的
executeTask
方法远程调用时,
Pi
类被装载到
ComputeEngine Server
的
JVM
中。根据启动
Client
时编码的
URL
类型,
Pi
类者从
client
的
web
服务器被下载,或者是从文件系统被下载。
When the ComputeEngine
server binds its remote object reference in the registry, the registry downloads the Compute
and Task
interfaces on which the stub class depends. These classes are downloaded from either the ComputeEngine
server's web server or file system, depending on the type of codebase URL used when starting the server.
Because the ComputePi
client has both the Compute
and the Task
interfaces available in its class path, it loads their definitions from its class path, not from the server's codebase.
Finally, the Pi
class is loaded into the ComputeEngine
server's Java virtual machine when the Pi
object is passed in the executeTask
remote call to the ComputeEngine
object. The Pi
class is loaded by the server from either the client's web server or file system, depending on the type of codebase URL used when starting the client.