Remote Method Invocation - Wrox Press Java Programming 24-Hour Trainer 2nd (2015)

Wrox Press Java Programming 24-Hour Trainer 2nd (2015)

Lesson 24. Remote Method Invocation

So far most of the Java programs in this tutorial have been running in a single Java virtual machine (JVM). There were two exceptions: In Lesson 16 in the section on “Socket Programming” you used two JVMs, and your JDBC programs from Chapter 21 communicated with another JVM running a database server. The application running on the user’s computer isn’t always allowed to access remote data directly—that’s one of the reasons distributed Java applications came into the picture. (The word distributed means having parts of the applications running on several computers.) The other reason was to provide a centralized server catering to multiple lightweight clients.

There are lots of ways to create Java distributed applications that run on more than one JVM, and Remote Method Invocation (RMI) is one of them even though it’s seldom used these days. For example, a client Java application (JVM1) connects to a server Java application (JVM2), which connects to the DBMS that runs on a third computer. The client application knows nothing about the DBMS; it gets the data, an ArrayList (or other data collection) of Employee objects, from the server’s application that runs in JVM2. RMI uses object serialization for the data exchange between JVM1 and JVM2.

But unlike with socket programming, where the client simply connects to a port on the server, with RMI one Java class can invoke methods on Java objects that live in another (remote) JVM. Although from a syntax perspective it looks as if the caller and the server’s class are located in the same JVM, they may be thousands of miles away. The RMI client won’t have a copy of the server-side method; it just has the method’s local representative—a proxy, or, using the RMI terminology, a stub.

Any RMI application consists of an RMI server, a client, and the registry (a naming service). These three components could run on three different JVMs running on different networked computers. The RMI server creates Java objects that implement business logic, registers them with the naming service, and waits for remote clients to invoke the server’s methods.

A client application gets a reference to a remote server object or objects from the registry and then invokes methods on this remote object. RMI uses the transport layer that hides the communications between the client’s stub and the server-side Java objects, and even though the methods are called in the client’s JVM, they are executed on the server’s. All RMI supporting classes and the registry tool are included with the Java SE.

Developing Applications with RMI

This lesson is written as an illustration of a sample RMI application with a minimum theory. For a more detailed description of RMI, refer to Oracle’s online tutorial on the subject.

Writing distributed RMI applications involves the following steps:

1. Declaring the remote interface.

2. Implementing the remote interface.

3. Writing a Java client that connects to the remote server and calls remote methods.

4. Starting the registry and registering the RMI server with it. The server associates (binds) its services with the registry names.

5. Starting the server and the client applications. The client looks up services by names and invokes them.

Let’s perform each of these steps by developing the RMI version of the Stock Quotes Server (see its version with sockets in socket programming), which provides a client with price quotes for a specified stock. Some of the preceding steps could be combined—for example, creating a registry and binding a service to it. The package java.rmi contains all RMI supporting classes used in the following code samples.

Defining Remote Interfaces

The Java classes that you are planning to deploy on the server side have to implement remote interfaces, which declare business method(s) to be invoked remotely by RMI clients. The client’s code looks as if it’s calling local methods, but these calls are redirected to a remote server via the RMI protocol. Following are the rules for creating remote interfaces:

· The remote interface must declare public methods to allow clients to perform remote method invocation.

· The remote interface must extend java.rmi.Remote.

· Each method must declare java.rmi.RemoteException.

· If method arguments are not primitives, they should be serializable objects to be able to travel across the network.

In RMI, development of a the server-side layer starts with answering the question, “What business methods have to be exposed to the client applications and what should their signatures be?” When you know the answer, define remote interfaces that declare those methods and classes that implement them. Let’s see how to apply this rule for the server that can serve stock prices.

Listing 24-1 shows the code of the StockServer remote interface that will be implemented on the server but also must exist on the client side. This interface declares two business methods: getQuote() and getNasdaqSymbols(). The first method generates a random price quote for the specified symbol, and the second returns the list of valid stock symbols.

Listing 24-1: StockServer interface

public interface StockServer extends Remote {

public String getQuote(String symbol) throws RemoteException;

public List<String> getNasdaqSymbols()throws RemoteException;

}

In RMI, class definitions are dynamically loaded from one JVM to another. Implementation of the classes resided on the server side may change—for example, new methods may be introduced to the implementation of the class. But the client sees the stubs with only the methods defined in the interface that extends Remote. In our example, to make more server-side methods available to the RMI client, you’d need to add them to the StockServer interface and make this new version available on both the client and the server.

Implementing Remote Interfaces

Although the remote interface just declares the methods, you need to create a class that runs on the server side and provides implementation for those methods. There is a special requirement to export such a class to the Java RMI runtime to enable the class to receive remote calls. This is somewhat similar to binding to a port in the case of ServerSocket (see Listing 16-5), but in the case of Java RMI, the server also creates a stub—a dummy class (for the client side) that contains proxies of each implemented method from remote interfaces.

One RMI client communicates to one server. It’s known as unicast as opposed to multicast (one to many) or broadcast (one to all). The easiest way to export an RMI server instance is by extending it from java.rmi.server.UnicastRemoteObject, as in Listing 24-2. If your server has to be extended from another class you can explicitly export the server object by calling UnicastRemoteObject.export().

Listing 24-2 shows an implementation of the class StockServerImpl, which processes the client’s requests. This class generates random price quotes for the stocks located in ArrayList nasdaqSymbols.

Listing 24-2: StockServerimpl class

public class StockServerImpl extends UnicastRemoteObject

implements StockServer {

private String price=null;

private ArrayList<String> nasdaqSymbols = new ArrayList<>();

public StockServerImpl() throws RemoteException {

super();

// Define some hard-coded NASDAQ symbols

nasdaqSymbols.add("AAPL");

nasdaqSymbols.add("MSFT");

nasdaqSymbols.add("YHOO");

nasdaqSymbols.add("AMZN");

}

public String getQuote(String symbol)

throws RemoteException {

if(nasdaqSymbols.indexOf(symbol.toUpperCase()) != -1) {

// Generate a random price for valid symbols

price = (new Double(Math.random()*100)).toString();

}

return price;

}

public ArrayList<String> getNasdaqSymbols()

throws RemoteException {

return nasdaqSymbols;

}

}

Registering Remote Objects

To make a remote object available to clients, you need to bind it to some name in a registry, a naming service that knows where exactly in the network your RMI server StockServerImpl is running. This allows Java clients to look up the object on the host machine by name.

Listing 24-3 depicts the code that binds the instance of StockServerImpl to port 1099 on the host machine, which is the local computer in my example. To the rest of the world this server is known as QuoteService.

Listing 24-3: Creating registry, starting the server, and binding it to a registry

public class StartServer {

public static void main (String args[]) {

try {

// Create the registry on port 1099

LocateRegistry.createRegistry(1099);

// Instantiate the StockServerInmpl and bind it

// to the registry under the name QuoteService

StockServerImpl ssi = new StockServerImpl();

Naming.rebind("rmi://localhost:1099/QuoteService",ssi);

}catch (MalformedURLException e1){

System.out.println(e1.getMessage());

}catch(RemoteException ex) {

ex.printStackTrace();

}

}

}

There are two methods in the class java.rmi.Naming that can bind an object in the registry. The method bind() binds an RMI server to a name. It throws AlreadyBoundException if the binding already exists. The method rebind() replaces any possibly existing binding with the new one. In addition to binding a server to a name, this also ensures that the clients requesting such services as getQuotes() or getNasdaqSymbols() receive their stubs—the local proxies of the remote methods.

The registry must be up and running by the time you start the program in Listing 24-3. One way to start the registry is by entering start rmiregistry in the Windows command window or rmiregistry in Mac OS. Instead of starting the registry manually, you can start it from within theStartServer program itself by calling the following method:

LocateRegistry.createRegistry(1099);

If you know that another process has already pre-created the registry, just get its reference and bind the server to it. The getRegistry() method can be called without arguments if the RMI registry runs on the default port 1099. If this is not the case, specify the port number (5048 in the following example). The variable registry in the following code fragment is a stub to the remote object StockServerImpl:

StockServerImpl ssi = new StockServerImpl();

Registry registry = LocateRegistry.getRegistry(5048);

registry.bind("QuoteService", ssi);

Writing RMI Clients

The client program, running anywhere on the Internet, performs a lookup in the registry on the host machine (using the host machine’s domain name or IP address) and obtains a reference to the remote object. Listing 24-4 shows a sample client program. Notice the casting to the StockServertype of the data returned by the method lookup().

Even though the class StockServerImpl has been bound to the name QuoteService, because this class implements the StockServer interface you can cast the returned object to it. The variable myServer sees only the methods defined in this interface, while the class StockServerImpl may have other public methods, too.

Listing 24-4: RMI client

public class Client {

public static void main (String args[]) {

if (args.length == 0) {

System.out.println(

"\n Sample usage: java client.Client AAPL");

System.exit(0);

}

try {

StockServer myServer = (StockServer)

Naming.lookup("rmi://localhost:1099/QuoteService");

String price = myServer.getQuote(args[0]);

if (price != null){

System.out.println("The price of " + args[0] +

" is: $" + price);

}

else{

System.out.println("Invalid Nasdaq symbol. " +

"Please use one of these:" +

myServer.getNasdaqSymbols().toString());

}

} catch (MalformedURLException exMF) {

System.out.println(exMF.getMessage());

} catch (NotBoundException exNB) {

System.out.println(exNB.getMessage());

} catch (RemoteException exRE) {

System.out.println(exRE.getMessage());

}

}

}

Multiple clients can connect to the same RMI server, but each client/server communication is done over the separate socket connection. This is done internally, so the application programmer doesn’t need to explicitly program sockets. If you remember, in Lesson 16 we had to invent a simple communication protocol in the example on socket connection. RMI uses its own proprietary protocol called JRMP, hence it can be used only in Java-to-Java communications.

Security Considerations

Can any RMI client restrict the actions that remotely loaded code can perform on the local computer? Can the server restrict access? You can specify a security policy file containing access restrictions. For example, in the code in Listing 24-3 and Listing 24-4 you can start the main() method with the following code:

if (System.getSecurityManager() == null) {

System.setSecurityManager(new RMISecurityManager());

}

The class java.rmi.RMISecurityManager extends the class java.lang.SecurityManager and provides a security context under which the RMI application executes. In RMI clients, the goal is to prevent remotely loaded stub code from downloading unsecured code via remote method invocation.

The RMI client uses a file in which security policies are defined. You can use the default security file, java.policy, located in your JDK or JRE installation directory under lib/security. The default policy file gives all permissions to the code, but you can create your own file and supply it either via the command-line parameter or in the code before the security manager is set:

System.setProperty("java.security.policy", "mypolicyfile");

For a more detailed description of security policy files, refer to the documentation at http://docs.oracle.com/javase/8/docs/technotes/guides/security/PolicyFiles.html.

Java applets can also serve as RMI clients, but they don’t need RMI security managers. The only restriction on them is that they can connect only to the RMI server running on the same host on which they were deployed.

Finding Remote Objects

RMI clients find remote services by using a naming or directory service. A naming service runs on a known host and port number. The subject of naming and directory services is covered in more detail in Chapter 29.

By now you know that an RMI server can start its own registry that offers naming services for RMI clients. The behavior of the registry is defined by the interface java.rmi.registry.Registry, and you saw an example of binding to the registry in the section “Registering Remote Objects”.

By default, the RMI registry runs on port 1099, unless another port number is specified. When the client wants to invoke methods on a remote object it obtains a reference to that object by looking up the name. The lookup returns to the client a remote reference, also known as a stub.

The method lookup() takes the object name’s URL as an argument in the following format:

rmi://<host_name>[:<name_service_port>]/<service_name>

host_name stands for the name of a computer on the local area network (LAN), or the name of a domain name system (DNS) on the Internet. name_service_port has to be specified only if the naming service is running on a port other than the default. service_name stands for the name of the remote object that should be bound to the registry.

Figure 24-1 illustrates the architecture of an RMI application. In the “Try It,” section you implement this architecture for the sample stock quote service.

image

Figure 24-1

Try It

In this exercise your goal is to start and test all the parts of the distributed Stock Server application, and you run all these parts on the same computer. The StartServer program creates a registry and binds the StockServerImpl under the name QuoteService. To emulate multiple computers you start the client from a command window. If everything is done properly you should be able to start the RMI client with one of the stock symbols known to the server, and get a price quote back.

Lesson Requirements

You should have Java installed.

NOTE You can download the code and resources for this “Try It” from the book’s web page at www.wrox.com/go/javaprog24hr2e. You can find them in the file Lesson24.zip.

Hints

There is an RMI plug-in for the Eclipse RMI that may be handy for developing RMI-based distributed applications. It contains a useful utility called RMI Spy that shows all outgoing and incoming method calls, and measures execution times. Another useful utility in this plug-in is Registry Inspector, which displays information about objects in the registry. The RMI plug-in is available from www.genady.net/rmi/index.html.

Step-by-Step

1. Import the Eclipse project called Lesson24. It has two packages: client and server.

2. Rebuild the project (use the menu Project → Clean). Note the location of the Eclipse workspace; you need it because you run all code samples from command windows. Make sure that after importing the project it points at the JRE that’s installed on your compute (right-click on the project name and use the menu Properties → Java Build Path).

3. Run the program StartServer to start and register StockServer with the registry (note the creation of registry with LocateRegistry ). After you enter a command similar to the one shown below, the server starts and binds to the registry, printing the following message in the Eclipse console:

<QuoteService> server is ready.

The server stays up and running waiting for requests from the clients.

4. You can run the client from Eclipse, too, but to better illustrate the fact that you use different JVMs, run the client from a command window. Open a command (or Terminal) window and go to the bin directory of the project Lesson24 in your Eclipse workspace. On my computer I did it as follows:

cd /Users/yfain11/practicalJava/workspace/Lesson24/bin

5. Run the Client program located in the client package, passing the stock symbol as a command-line argument, and the Client connects to your “remote” server and receives the price quote—for example:

java client.Client AAPL

On my computer the output looked as follows:

The price of AAPL is: $91.85776369781252

6. Open several command windows and run the client program in each of them, emulating multiple clients sending requests to the same server.

7. Add the following statement to the method getQuote() of StockServerImpl class and note how the server reports each quote request in the Eclipse console:

System.out.println("Got the price quote request for " +
symbol);

TIP Please select the videos for Lesson 24 online at www.wrox.com/go/javaprog24hr2e. You will also be able to download the code and resources for this lesson from the website.