rmi

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:
1. Designing and implementing the components of your distributed application.
2. Compiling sources.
3. Making classes network accessible.
4. 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:
package compute;

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:
package compute;

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:

package engine;

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:

public class ComputeEngine implements Compute

这个声明语句说明的意思是:这个类实现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:

    public ComputeEngine() {
        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:

public <T> T executeTask(Task<T> t) {
        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 an int 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, or rebind 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. A lookup, 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:
package compute;

public interface Task<T> {
    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:
package client;

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:
package client;

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 and Task interfaces
• engine – ComputeEngine implementation class
• client – ComputePi client code and Pi 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
adding: 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
     -Djava.security.policy=server.policy
        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 the java.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 the java.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 the java.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.


你可能感兴趣的:(java,Web)