Monday, October 9, 2017

Java RMI – Java Remote Method Invocation Example

 

1. What is RMI

RMI (Remote Method Invocation) is an object-oriented way of RPC (Remote Procedure Call) to realize communication between distributed objects in a distributed computing environment. It allows an object to invoke methods on a remote object.

Java RMI, a Java implementation of remote method invocation, consists of several APIs under java.rmi package. It allows a Java program running on one Java virtual machine (client) to invoke methods on another Java virtual machine (server). Especially it supports transferring serialized Java objects from machine to machine over the network which makes automatic management of distributed objects come true. With the help of Java RMI, the complexity of working with local and remote objects becomes minimal. The type safety is preserved and distributed garbage collection (DGC) becomes possible.

The diagram below shows the core components of Java RMI and how Java RMI works. We have simplified the diagram and will go through all the technical details in section 3 when building an example.

Java RMI - How Java RMI works
Fig. 1. How Java RMI works

2. When is Java RMI used

From the diagram above we can see that Java RMI is basically a client-server model. It can be used when we want to communicate with different Java virtual machines. These Java virtual machines can be on different hosts or on the same host. In addition to the benefits mentioned in the previous section, one advantage of Java RMI is that we don’t need to re-invent the wheel. The business logic can be implemented and maintained in one place and be re-used in other places. But as the invocation process involves serialization/deserialization and network transport, it is slow and not very reliable. Unexpected errors such as network error could happen during the invocation.

3. Building a simple product information service by using Java RMI

Imagine we have a product inventory for an e-commerce website. The product inventory contains ten thousand products. Each of them has a unique id and name. Our staff needs to look up product information on different devices from different places such as desktops, mobile phones, or handheld terminals. To meet these requirements, we can build a product information server holding the lookup logic and product information clients deployed on different devices to look up product information from the server. Let’s start building it step by step using Java RMI.

3.1 Defining the Contract

In order to communicate between an RMI server and an RMI client, we need to define a contract known to both ends. Java interfaces are used for this purpose. RMI remote interfaces must extend java.rmi.Remote interface. The ProductInfoService interface defines methods we would like to expose to clients remotely. To make things simple, we define one method getProductInfoById(int id) which will return the product info by the given product Id. Note that the method must throw java.rmi.RemoteException.

ProductInfoService.java
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public interface ProductInfoService extends Remote {
    /**
     * The name used in the RMI registry.
     */
    static final String SERVICE_NAME = "ProductInfoService";
 
    /**
     * Get product info by the given Id.
     *
     * @param id the product id
     * @return a ProductInfo instance
     * @throws RemoteException
     */
    ProductInfo getProductInfoById(int id) throws RemoteException;
}

3.2 Creating the Product Information Server

Once we have defined the contract, we can start building the Product Information server. There are two parts that need to be built: the service implementation and the RMI server.

In our example, the ProductInfoServiceImpl class implements ProductInfoService and extends java.rmi.server.UnicastRemoteObject class. By extending java.rmi.server.UnicastRemoteObject class, the ProductInfoServiceImpl can export a remote object with JRMP (Java Remote Method Protocol) and obtain a stub that communicates to the remote object. Firstly, we define a POJO ProductInfo with two fields: id and name. Note that ProductInfo must implement java.io.Serializable and we need to make sure the ProductInfo class on both server and client sides has the same serialVersionUID. Otherwise serialization and deserialization will fail during the remote invocation.

ProductInfo.java
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public class ProductInfo implements Serializable {
    // important: make sure the class on both client and server sides have the same value
    private static final long serialVersionUID = 1L;
 
    private int id;
 
    private String name;
 
    /**
     * Constructor.
     *
     * @param id
     * @param name
     */
    public ProductInfo(int id, String name) {
        super();
        this.id = id;
        this.name = name;
    }
 
    /**
     * @return the id
     */
    public int getId() {
        return id;
    }
 
    /**
     * @param id the id to set
     */
    public void setId(int id) {
        this.id = id;
    }
 
    /**
     * @return the name
     */
    public String getName() {
        return name;
    }
 
    /**
     * @param name the name to set
     */
    public void setName(String name) {
        this.name = name;
    }
 
    @Override
    public String toString() {
        return "ProductInfo [id=" + id + ", name=" + name + "]";
    }
 
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + id;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
 
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        ProductInfo other = (ProductInfo) obj;
        if (id != other.id)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
}

Then in the implementation of getProductInfoById(int id) method, we simply return a new ProductInfo instance with the id and the name.

ProductInfoServiceImpl.java
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ProductInfoServiceImpl extends UnicastRemoteObject implements ProductInfoService {
 
    private static final long serialVersionUID = 1L;
 
    /**
     * Constructs the service.
     *
     * @throws RemoteException
     */
    protected ProductInfoServiceImpl() throws RemoteException {
        super();
    }
 
    /**
     * Get the product info by the given id.
     *
     * @param id the product id
     * @return a ProductInfo instance
     */
    public ProductInfo getProductInfoById(int id) throws RemoteException {
        return new ProductInfo(id, "Sample Product");
    }
}

The product information server will construct an instance of the ProductInfoService and registers it with the RMI registry. The RMI registry is a separate program shipped with the JDK and you can run it from the command line by typing rmiregistry. It will run at port 1099 by default. For your convenience, we will start the RMI registry programmatically on the localhost at port 1099. The RMI server will run for about 10 seconds waiting for any RMI request from clients. Then it will shut down the RMI registry and quit.

ProductInfoServer.java
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class ProductInfoServer {
    /**
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("ProductInfoServer is starting...");
 
        try {
            // create a RMI registry on localhost at port 1099
            Registry registry = LocateRegistry.createRegistry(1099);
 
            System.out.println("RMI registry is running on port 1099");
 
            // create an instance of the service object
            ProductInfoService service = new ProductInfoServiceImpl();
 
            System.out.println("Binding ProductInfoService...");
 
            // bind it in the RMI registry
            registry.rebind(ProductInfoService.SERVICE_NAME, service);
 
            System.out.println("ProductInfoService is ready.");
 
            System.out.println("Wait for 10 seconds for any incoming client call before terminating the RMI registry...");
 
            // sleep 10 seconds
            Thread.sleep(10000);
 
            // unbind the service object
            registry.unbind(ProductInfoService.SERVICE_NAME);
 
            // remove the service object from the registry
            UnicastRemoteObject.unexportObject(service, true);
 
            System.out.println("Shutting down the RMI registry...");
 
            // shut down the registry
            UnicastRemoteObject.unexportObject(registry, true);
 
            System.out.println("ProductInfoServer has stopped.");
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }
}

Compile and run the product information server from the IDE or the command line, we can see the following output on standard output.

1
2
3
4
5
6
7
ProductInfoServer is starting...
RMI registry is running on port 1099
Binding ProductInfoService...
ProductInfoService is ready.
Wait for 10 seconds for any incoming client call before terminating the RMI registry...
Shutting down the RMI registry...
ProductInfoServer has stopped.

3.3 Creating an RMI Client

After the product information server is up and running, how can we use the product information service provided by the server? An RMI client comes into play. We create an RMI client named ProductInfoClient to locate the remote service object and call its method. In the client we use java.rmi.Naming class to obtaining a reference to the ProductInfoService remote object in the RMI registry running on the localhost at port 1099. Then we can simply call the getProductInfoById(int id) method with an id “123” and print the returned product information to standard output.

ProductInfoClient.java
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
public class ProductInfoClient {
    /**
     * @param args
     */
    public static void main(String[] args) {
        try {
            System.out.println("ProductInfoClient> get product info with id '123'...");
            // looks up the registry by service name and returns a stub
            ProductInfoService productInfoService = (ProductInfoService) Naming.lookup(ProductInfoService.SERVICE_NAME);
            // invoke the remote method via the stub
            ProductInfo productInfo = productInfoService.getProductInfoById(123);
            System.out.println("ProductInfoClient> production info received: " + productInfo.toString());
        } catch (Exception e) {
            System.err.println("ProductInfoClient> RemoteDate exception: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

Compile and run the product information client from the IDE or the command line, we can see the following output on standard output.

1
2
ProductInfoClient> get product info with id '123'...
ProductInfoClient> production info received: ProductInfo [id=123, name=Sample Product]

4. Java RMI RemoteException

Since there is a chance of network issues during remote invocations, an exception named RemoteException may occur. If the method invocation results in an exception being thrown, the exception is indicated to the caller. For more information about Java RMI RemoteException and how to handle it properly, please see this example: java.rmi.RemoteException – How to solve RemoteException

5. Download the Source Code

Download
You can download the full source code of this example here: Java RMI – Java Remote Method Invocation Example

No comments:

Post a Comment

CORBA Java Tutorial using Netbeans and Java 8.

CORBA-Example A simple CORBA implementation using Java Echo.idl module EchoApp{ interface Echo{ string echoString(); }; }; ...