Tuesday 28 March 2017

Reflection & Remote Method Invocation (RMI) - Java Tutorials

Reflection

Reflection is the ability of software to analyze itself. This is provided by the java.lang.reflect package and elements in Class. Reflection is an important capability, needed when using components called Java Beans. It allows you to analyze a software component and describe its capabilities dynamically, at run time rather than at compile time. For example, by using reflection, you can determine what methods, constructors, and fields a class supports.

The package java.lang.reflect has an interface, called Member, which defines methods that allow you to get information about a field, constructor, or method of a class. There are also eight classes in this package. 

The following application illustrates a simple use of the Java reflection capabilities. It prints the constructors, fields, and methods of the class java.awt.Dimension. The program begins by using the forName( ) method of Class to get a class object for java.awt.Dimension. Once this is obtained, getConstructors( ), getFields( ), and getMethods( ) are used to analyze this class object. They return arrays of Constructor, Field, and Method objects that provide the information about the object. The Constructor, Field, and Method classes define several methods that can be used to obtain information about an object. You will want to explore these on your own. However, each supports the toString( ) method. Therefore, using Constructor, Field, and Method objects as arguments to the println( ) method is straightforward, as shown in the program.


Classes Defined in java.lang.reflect

AccessibleObject:  Allows you to bypass the default access control checks. (Added by Java 2)

Array:  Allows you to dynamically create and manipulate arrays.

Constructor:  Provides information about a constructor.

Field:  Provides information about a field.

Method:  Provides information about a method.

Modifier:  Provides information about class and member access modifiers.

Proxy:  Supports dynamic proxy classes. (Added by Java 2, v1.3)

ReflectPermission:  Allows reflection of private or protected members of a class. (Added by Java 2)


  // Demonstrate reflection.
  import java.lang.reflect.*;
  public class ReflectionDemo1 {
    public static void main(String args[]) {
      try {
        Class c = Class.forName("java.awt.Dimension");
        System.out.println("Constructors:");
        Constructor constructors[] = c.getConstructors();
        for(int i = 0; i < constructors.length; i++) {
          System.out.println(" " + constructors[i]);
        }

        System.out.println("Fields:");
        Field fields[] = c.getFields();
        for(int i = 0; i < fields.length; i++) {
          System.out.println(" " + fields[i]);
        }

        System.out.println("Methods:");
        Method methods[] = c.getMethods();
        for(int i = 0; i < methods.length; i++) {
          System.out.println(" " + methods[i]);
        }
      }
      catch(Exception e) {
        System.out.println("Exception: " + e);
      }
    }
  }

Here is the output from this program:

  Constructors:
   public java.awt.Dimension(java.awt.Dimension)
   public java.awt.Dimension(int,int)
   public java.awt.Dimension()
  Fields:
   public int java.awt.Dimension.width
   public int java.awt.Dimension.height
  Methods:
   public int java.awt.Dimension.hashCode()
   public boolean java.awt.Dimension.equals(java.lang.Object)
   public java.lang.String java.awt.Dimension.toString()
   public void java.awt.Dimension.setSize(java.awt.Dimension)
   public void java.awt.Dimension.setSize(int,int)
   public void java.awt.Dimension.setSize(double,double)
   public java.awt.Dimension java.awt.Dimension.getSize()
   public double java.awt.Dimension.getWidth()
   public double java.awt.Dimension.getHeight()
   public java.lang.Object java.awt.geom.Dimension2D.clone()
   public void java.awt.geom.Dimension2D.
     setSize(java.awt.geom.Dimension2D)
   public final native java.lang.Class java.lang.Object.getClass()
   public final void java.lang.Object.wait(long,int) throws
     java.lang.InterruptedException
   public final void java.lang.Object.wait()
     throws java.lang.InterruptedException
   public final native void java.lang.Object.wait(long) throws
     java.lang.InterruptedException
   public final native void java.lang.Object.notify()
   public final native void java.lang.Object.notifyAll()


The next example uses Java’s reflection capabilities to obtain the public methods of a class. The program begins by instantiating class A. The getClass( ) method is applied to this object reference and it returns the Class object for class A. The getDeclaredMethods( ) method returns an array of Method objects that describe only the methods declared by this class. Methods inherited from superclasses such as Object are not included.

Each element of the methods array is then processed. The getModifiers( ) method returns an int containing flags that describe which access modifiers apply for this element. The Modifier class provides a set of methods, shown below, that can be used to examine this value. For example, the static method isPublic( ) returns true if its argument includes the public access modifier. Otherwise, it returns false. In the following program, if the method supports public access, its name is obtained by the getName( ) method and is then printed.


Methods Defined by Modifier That Determine Access Modifiers

static boolean isAbstract(int val):  Returns true if val has the abstract flag set and false otherwise.

static boolean isFinal(int val):  Returns true if val has the final flag set and false otherwise.

static boolean isInterface(int val):  Returns true if val has the interface flag set and false otherwise.

static boolean isNative(int val):  Returns true if val has the native flag set and false otherwise.

static boolean isPrivate(int val):  Returns true if val has the private flag set and false otherwise.

static boolean isProtected(int val):  Returns true if val has the protected flag set and false otherwise.

static boolean isPublic(int val):  Returns true if val has the public flag set and false otherwise.

static boolean isStatic(int val):  Returns true if val has the static flag set and false otherwise.

static boolean isStrict(int val):  Returns true if val has the strict flag set and false otherwise.

static boolean isSynchronized(int val):  Returns true if val has the synchronized flag set and false otherwise.

static boolean isTransient(int val):  Returns true if val has the transient flag set and false otherwise.

static boolean isVolatile(int val):  Returns true if val has the volatile flag set and false otherwise.


  // Show public methods.
  import java.lang.reflect.*;
  public class ReflectionDemo2 {
    public static void main(String args[]) {
      try {
        A a = new A();
        Class c = a.getClass();
        System.out.println("Public Methods:");
        Method methods[] = c.getDeclaredMethods();
        for(int i = 0; i < methods.length; i++) {
          int modifiers = methods[i].getModifiers();
          if(Modifier.isPublic(modifiers)) {
            System.out.println(" " + methods[i].getName());
          }
        }
      }
      catch(Exception e) {
        System.out.println("Exception: " + e);
      }
    }
  }
  
  class A {
    public void a1() {
    }
    public void a2() {
    }
    protected void a3() {
    }
    private void a4() {
    }
  }

Here is the output from this program:

  Public Methods:
   a1
   a2




Remote Method Invocation (RMI)

Remote Method Invocation (RMI) allows a Java object that executes on one machine to invoke a method of a Java object that executes on another machine. This is an important feature, because it allows you to build distributed applications. While a complete discussion of RMI is outside the scope of this book, the following example describes the basic principles involved.


A Simple Client/Server Application Using RMI

This section provides step-by-step directions for building a simple client/server application by using RMI. The server receives a request from a client, processes it, and returns a result. In this example, the request specifies two numbers. The server adds these together and returns the sum.

Step One: Enter and Compile the Source Code
This application uses four source files. The first file, AddServerIntf.java, defines the remote interface that is provided by the server. It contains one method that accepts two double arguments and returns their sum. All remote interfaces must extend the Remote interface, which is part of java.rmi. Remote defines no members. Its purpose is simply to indicate that an interface uses remote methods. All remote methods can throw a RemoteException.

  import java.rmi.*;
  public interface AddServerIntf extends Remote {
    double add(double d1, double d2) throws RemoteException;
  }

The second source file, AddServerImpl.java, implements the remote interface. The implementation of the add( ) method is straightforward. All remote objects must extend UnicastRemoteObject, which provides functionality that is needed to make objects available from remote machines.

  import java.rmi.*;
  import java.rmi.server.*;
  public class AddServerImpl extends UnicastRemoteObject
    implements AddServerIntf {

    public AddServerImpl() throws RemoteException {
    }
    public double add(double d1, double d2) throws RemoteException {
      return d1 + d2;
    }
  }

The third source file, AddServer.java, contains the main program for the server machine. Its primary function is to update the RMI registry on that machine. This is done by using the rebind( ) method of the Naming class (found in java.rmi). That method associates a name with an object reference. The first argument to the rebind( ) method is a string that names the server as “AddServer”. Its second argument is a reference to an instance of AddServerImpl.

  import java.net.*;
  import java.rmi.*;
  public class AddServer {
    public static void main(String args[]) {
      try {
        AddServerImpl addServerImpl = new AddServerImpl();
        Naming.rebind("AddServer", addServerImpl);
      }
      catch(Exception e) {
        System.out.println("Exception: " + e);
      }
    }
  }

The fourth source file, AddClient.java, implements the client side of this distributed application. AddClient.java requires three command line arguments. The first is the IP address or name of the server machine. The second and third arguments are the two numbers that are to be summed.

The application begins by forming a string that follows the URL syntax. This URL uses the rmi protocol. The string includes the IP address or name of the server and the string “AddServer”. The program then invokes the lookup( ) method of the Naming class. This method accepts one argument, the rmi URL, and returns a reference to an object of type AddServerIntf. All remote method invocations can then be directed to this object.

The program continues by displaying its arguments and then invokes the remote add( ) method. The sum is returned from this method and is then printed.

  import java.rmi.*;
  public class AddClient {
    public static void main(String args[]) {
      try {
        String addServerURL = "rmi://" + args[0] + "/AddServer";
        AddServerIntf addServerIntf =
                     (AddServerIntf)Naming.lookup(addServerURL);
        System.out.println("The first number is: " + args[1]);
        double d1 = Double.valueOf(args[1]).doubleValue();
        System.out.println("The second number is: " + args[2]);

        double d2 = Double.valueOf(args[2]).doubleValue();
        System.out.println("The sum is: " + addServerIntf.add
                          (d1, d2));
      }
      catch(Exception e) {
        System.out.println("Exception: " + e);
      }
    }
  }

After you enter all the code, use javac to compile the four source files that you created.

Step Two: Generate Stubs and Skeletons
Before you can use the client and server, you must generate the necessary stub. You may also need to generate a skeleton. In the context of RMI, a stub is a Java object that resides on the client machine. Its function is to present the same interfaces as the remote server. Remote method calls initiated by the client are actually directed to the stub. The stub works with the other parts of the RMI system to formulate a request that is sent to the remote machine.

A remote method may accept arguments that are simple types or objects. In the latter case, the object may have references to other objects. All of this information must be sent to the remote machine. That is, an object passed as an argument to a remote method call must be serialized and sent to the remote machine.  that the serialization facilities also recursively process all referenced objects.

Skeletons are not required by Java 2. However, they are required for the Java 1.1 RMI model. Because of this, skeletons are still required for compatibility between Java 1.1 and Java 2. A skeleton is a Java object that resides on the server machine. It works with the other parts of the 1.1 RMI system to receive requests, perform deserialization, and invoke the appropriate code on the server. Again, the skeleton mechanism is not required for Java 2 code that does not require compatibility with 1.1. Because many readers will want to generate the skeleton, one is used by this example.

If a response must be returned to the client, the process works in reverse. Note that the serialization and deserialization facilities are also used if objects are returned to a client. To generate stubs and skeletons, you use a tool called the RMI compiler, which is invoked from the command line, as shown here:

      rmic AddServerImpl

This command generates two new files: AddServerImpl_Skel.class (skeleton) and AddServerImpl_Stub.class (stub). When using rmic, be sure that CLASSPATH is set to include the current directory. As you can see, by default, rmic generates both a stub and a skeleton file. If you do not need the skeleton, you have the option to suppress it.

Step Three: Install Files on the Client and Server Machines
Copy AddClient.class, AddServerImpl_Stub.class, and AddServerIntf.class to a directory on the client machine. Copy AddServerIntf.class, AddServerImpl.class, AddServerImpl_Skel.class, AddServerImpl_Stub.class, and AddServer.class to a directory on the server machine.

RMI has techniques for dynamic class loading, but they are not used by the example at hand. Instead, all of the files that are used by the client and server applications must be installed manually on those machines.

Step Four: Start the RMI Registry on the Server Machine
The Java 2 SDK provides a program called rmiregistry, which executes on the server machine. It maps names to object references. First, check that the CLASSPATH environment variable includes the directory in which your files are located. Then, start the RMI Registry from the command line, as shown here:

      start rmiregistry

When this command returns, you should see that a new window has been created. You need to leave this window open until you are done experimenting with the RMI example.

Step Five: Start the Server
The server code is started from the command line, as shown here:

      java AddServer

Recall that the AddServer code instantiates AddServerImpl and registers that object with the name “AddServer”.

Step Six: Start the Client
The AddClient software requires three arguments: the name or IP address of the server machine and the two numbers that are to be summed together. You may invoke it from the command line by using one of the two formats shown here:

      java AddClient server1 8 9
      java AddClient 11.12.13.14 8 9

In the first line, the name of the server is provided. The second line uses its IP address (11.12.13.14). You can try this example without actually having a remote server. To do so, simply install all of the programs on the same machine, start rmiregistry, start AddSever, and then execute AddClient using this command line:

      java AddClient 127.0.0.1 8 9

Here, the address 127.0.0.1 is the “loop back” address for the local machine. Using this address allows you to exercise the entire RMI mechanism without actually having to install the server on a remote computer. In either case, sample output from this program is shown here:

  The first number is: 8
  The second number is: 9
  The sum is: 17.0

No comments:

Post a Comment