Using RMI to Communicate with Enterprise JavaBeans

   

One of the key aspects of the EJB architecture is its distributed nature. By distributed, we mean that all the objects might not be located within the same JVM. So the question becomes, how can you invoke methods from a Java object in JVM A on a Java object in JVM B? The EJB answer is through Remote Method Invocation (RMI). RMI predates EJB and Java. In fact, it has been used by other distributed technologies, such as CORBA and DCOM. For Java, there is a specific version called Java RMI. Java RMI is a distributed object protocol that is specifically designed to allow Java objects to communicate with other Java objects residing in different Java virtual machines. Java RMI is specifically Java-to-Java remote communication. Figure 3.7 shows how a client and server use RMI to communicate.

Figure 3.7. A remote Java client communicates with a Java RMI server.

graphics/03fig07.gif

Java RMI provides transparency to the client objects making calls on remote objects so that it appears that the remote object is located within the same JVM as the client. The Java RMI protocol provides this transparency through two components , the stub and skeleton .

What Are Stubs and Skeletons?

Invoking a method call on a Java object that resides in a different JVM has much more overhead than one located within the same JVM. The client object must first be able to locate the remote object over the network and then invoke a particular method and pass along any parameters to the remote object over the network. On the server side, the object that is receiving the call must be listening for incoming requests and somehow provide for things like security and synchronous message handling so that data returned from the method or exceptions thrown by the remote object can be returned to the client. Care also has to be taken to make sure that responses to requests are returned to the correct client.

If you had to implement this functionality for your Java classes that needed to talk to remote objects, it would make for some tedious work. This would also be a waste of your time because no real business logic is happening in the remote calls. It's all code to handle the fact that you are invoking an operation on a remote object. The only thing that is really different from one remote Java class to another is the specific Java methods that are being invoked, the parameters that are passed, and the return values or exceptions thrown. Fortunately, tools can be used to inspect your classes and generate all the necessary code to handle the remote calls. This helps reduce the complexity of your classes and also decouples the business domain from the infrastructure of communicating with the remote objects. This is exactly the purpose of the stub and skeleton classes. The stub object is a remote proxy that is responsible for processing the remote method invocation. A remote proxy is an object that hides the fact that the client is communicating with a remote object and usually implements the method signatures of the remote object exactly. The stub serves as a proxy for the RMI client in the following manner:

  1. Initiates a connection with the object in the remote JVM.

  2. Marshals ( writes and transmits) the parameters to the remote JVM.

  3. Waits for the result of the method invocation.

  4. Unmarshals (reads) the return value or exception returned.

  5. Returns the value or exception to the caller.

The stub object exposes the same business methods and signatures as the actual remote instances. In the remote JVM, each remote object might have a corresponding skeleton object associated with it. The skeleton object is responsible for dispatching a client invocation to the correct remote instance. Normally there is one skeleton for every remote object. However, since the Java 2 platform was released, a skeleton is no longer necessary. This is because there can be generic code that handles the dispatching of client requests to the remote instance. However, it still helps to think of skeleton objects being used, even if the implementation can be different.

When a skeleton receives an incoming method invocation, it does the following:

  1. Unmarshals (reads) the parameters for the remote method.

  2. Invokes the method on the actual remote object implementation.

  3. Marshals (writes and transmits) the result (return value or exception) to the caller.

For every enterprise bean that is made available to remote clients , a stub and possibly a skeleton object are created. The stub and skeleton are normally created at the time that a bean is deployed into the container and must be done with the tools provided by the vendor. How the container uses the stub and skeleton to handle the remote method invocation or whether there is a skeleton object created at all, depends on the vendor. As a bean provider or assembler, you should not have to worry about how it works, unless you are the curious type. If you are the curious type, we'll give a brief example of what takes place during a remote method invocation. To keep things simple for now, we'll show an example using Java RMI over Java Remote Method Protocol (JRMP). As we'll see later in this chapter, Java RMI can also use the Object Management Group's (OMG) Internet Inter-Orb Protocol (IIOP) as the communication protocol. This small example should be enough to give you the idea of how RMI is generally done. You'll see examples of using RMI/IIOP throughout the book.

We are going to create a remote object that implements an interface with just one method. Listing 3.1 shows the Java interface for which our remote object will provide an implementation.

Listing 3.1 A Remote Interface That the Remote Object Will Implement
 import java.rmi.Remote;  import java.rmi.RemoteException;  public interface RMIExample extends Remote {   public String getMessage() throws RemoteException;  } 

The only method that is declared by our RMI interface is the getMessage method. Notice that the interface extends the java.rmi.Remote interface. Also, because calling remote methods can fail in ways that are not possible with local method calls due to network issues, all methods defined in a Remote interface must include java.rmi.RemoteException in their throws clause.

Next , we need to provide a class that implements the remote interface. This is our remote object that eventually will receive the remote method invocation from the client. Listing 3.2 shows the remote object.

Listing 3.2 The Remote Object for Our Example
 Import java.rmi.*;  import java.rmi.RemoteException;  import java.rmi.server.UnicastRemoteObject;  public class RMIExampleImpl extends UnicastRemoteObject    implements RMIExample {     public RMIExampleImpl() throws RemoteException  {       super();      }      public String getMessage() {       return "Hello from the RMI Server";      }  } 

Listing 3.2 is fairly straightforward. It extends java.rmi.server.UnicastRemoteObject , which is a class that takes care of many of the complex network issues for you.

Note

Remember for this example, we are showing Java RMI using JRMP as the communication protocol. JRMP is the default communications or "wire" protocol for allowing remote Java objects to communicate. Later in this chapter, you'll see an example of Java RMI using the IIOP protocol. This is the protocol that EJB uses by default.


The RMIExampleImpl class also implements the RMIExample interface and provides the required getMessage method by just returning a Hello World kind of string. Notice that the implementation does not throw the RemoteException . The network and marshalling aspects of the remote call are handled by the stub or skeleton object. In Java, you can't add any new exceptions to an interface method, but you can take them away as this example did.

Now, it's time to take a look at what the stub and skeleton classes would look like for this example. Be warned , it will not be pretty, but that's acceptable because you rarely have to look at or be concerned about them. If you run the rmic tool that comes with the Java SDK (it's located in the bin directory of the base Java home directory), the stub and skeleton classes will be generated. The rmic compiler generates stub and skeleton class files for remote objects from the names of compiled Java classes that contain remote object implementations . A remote object is one that implements the interface java.rmi.Remote . The classes named in the rmic command must be classes that have been compiled successfully with the javac command and must be fully qualified. Be sure to use the -keep option if you want the rmic tool to provide you with the source files as well as the class files.

Listing 3.3 shows the stub that gets generated, and Listing 3.4 shows the skeleton class. We have modified the format to make it fit a little cleaner on the written page.

Listing 3.3 Stub Class for RMIExampleImpl Class
 // Stub class generated by rmic, do not edit.  // Contents subject to change without notice.  import java.rmi.*;  public final class RMIExampleImpl_Stub      extends java.rmi.server.RemoteStub      implements RMIExample, java.rmi.Remote  {     private static final java.rmi.server.Operation[] operations = {     new java.rmi.server.Operation("java.lang.String getMessage()")      };      private static final long interfaceHash = 6819080097909274298L;      private static final long serialVersionUID = 2;      private static boolean useNewInvoke;      private static java.lang.reflect.Method $method_getMessage_0;      static {     try {         java.rmi.server.RemoteRef.class.getMethod("invoke",          new java.lang.Class[] {             java.rmi.Remote.class,              java.lang.reflect.Method.class,              java.lang.Object[].class,              long.class          });          useNewInvoke = true;          $method_getMessage_0 =             RMIExample.class.getMethod("getMessage", new java.lang.Class[] {});      } catch (java.lang.NoSuchMethodException e) {         useNewInvoke = false;      }      }      // constructors      public RMIExampleImpl_Stub() {     super();      }      public RMIExampleImpl_Stub(java.rmi.server.RemoteRef ref) {     super(ref);      }      // methods from remote interfaces      // implementation of getMessage()      public java.lang.String getMessage()      throws java.rmi.RemoteException      {     try {         if (useNewInvoke) {         Object $result =            ref.invoke(this, $method_getMessage_0, null, 5353407034680111516L);          return ((java.lang.String) $result);          } else {         java.rmi.server.RemoteCall call =            ref.newCall((java.rmi.server.RemoteObject) this,                        operations, 0, interfaceHash);          ref.invoke(call);          java.lang.String $result;          try {             java.io.ObjectInput in = call.getInputStream();              $result = (java.lang.String) in.readObject();          } catch (java.io.IOException e) {             throw new UnmarshalException("error unmarshalling return", e);          } catch (java.lang.ClassNotFoundException e) {             throw new UnmarshalException("error unmarshalling return", e);          } finally {             ref.done(call);          }          return $result;          }      } catch (java.lang.RuntimeException e) {         throw e;      } catch (java.rmi.RemoteException e) {         throw e;      } catch (java.lang.Exception e) {         throw new UnexpectedException("undeclared checked exception", e);      }      }  } 
Listing 3.4 Skeleton Class for RMIExampleImpl Class
 // Skeleton class generated by rmic, do not edit.  // Contents subject to change without notice.  import java.rmi.server.*;  public final class RMIExampleImpl_Skel      implements java.rmi.server.Skeleton  {     private static final java.rmi.server.Operation[] operations = {     new java.rmi.server.Operation("java.lang.String getMessage()")      };      private static final long interfaceHash = 6819080097909274298L;      public java.rmi.server.Operation[] getOperations() {     return (java.rmi.server.Operation[]) operations.clone();      }      public void dispatch(java.rmi.Remote obj,               java.rmi.server.RemoteCall call, int opnum, long hash)      throws java.lang.Exception      {     if (opnum < 0) {         if (hash == 5353407034680111516L) {         opnum = 0;          } else {         throw new java.rmi.UnmarshalException("invalid method hash");          }      } else {         if (hash != interfaceHash)          throw new SkeletonMismatchException("interface hash mismatch");      }      RMIExampleImpl server = (RMIExampleImpl) obj;      switch (opnum) {     case 0: // getMessage()      {         call.releaseInputStream();          java.lang.String $result = server.getMessage();          try {         java.io.ObjectOutput out = call.getResultStream(true);          out.writeObject($result);          } catch (java.io.IOException e) {         throw new java.rmi.MarshalException("error marshalling return", e);          }          break;      }      default:          throw new java.rmi.UnmarshalException("invalid method number");      }      }  } 

We warned you that it wasn't going to be pretty. There's quite a bit going on in these two classes. We are not going to go through them, but take a look at the stub class and see if you can see how it's invoking the method on the remote object. Then take a look at the skeleton and see if you see how it determines which method to call and how it returns the results from the method call back to the remote client. Again, the details are not as important as the fact that you realize that something is going on behind the scenes when a client invokes a method on a remote object. It's enough that you understand that a stub object, and in some cases, a skeleton, are working on your behalf to help complete the remote method invocation. Again, since Java 2, skeletons are not always implemented. However, there's still code on the server side to handle the dispatching of client requests.

Even though each EJB vendor may implement RMI for EJB slightly different from other vendors , and even though the container is in the middle of all this, the concepts are still the same. The client makes a call-by-reference onto a remote interface, which is implemented by a stub object, which is located in the same JVM as the client. The stub class determines which method needs to be invoked on the remote object and packages all the parameter data up so that it can be marshaled across the network. A skeleton object, or some alternative to a skeleton object, receives the call and gets it routed to an appropriate remote object. As you'll see in the next sections, there's a little more to RMI with EJB, but it's pretty much the same idea.

When doing Java RMI, you must create an interface that describes all the business methods that will be called on the remote object. This is what we called the component interface from the previous section. In the case of RMI, the component interface is a remote interface. This interface serves as a contract between the remote calling client and the server object that is receiving the message and servicing the request. The container also uses this component interface to build the stub and skeleton objects for your bean. This interface describes the client/server contract in terms of what methods might be invoked by a client. The stub is a Java class that implements the remote interface and is typically generated by the vendor tools during deployment. The remote interface and the stub object that implements it serve as a remote proxy for the client. All calls that the client makes on the remote interface are really handled by the stub class and are sent across the network to the server implementation.

The most important aspect to take away from this section is that when you invoke a method call on an EJB object from a remote client, you are not invoking a call on the real bean instance directly. The manner in which the vendor implements stubs and skeletons has much to do with this. This allows the vendor to do optimizations for better performance and scalability. Figure 3.8 shows how a typical EJB application uses RMI.

Figure 3.8. An EJB client uses RMI to communicate with enterprise beans.

graphics/03fig08.gif

For more information on Java RMI, check the Sun documentation at

http://java.sun.com/j2se/1.3/docs/guide/rmi

There is also a good tutorial on Java RMI at the same location.

Using RMI Over IIOP

One of the main issues with Java's version of RMI is that a JVM must be running on both the client and the server. It's dependent on Java being the language for the application on the client and the server. With the amount of so-called legacy systems that are written in other languages like C++, Java needs a way to communicate with these systems. As you read earlier in this chapter, Java RMI uses JRMP by default. It would be nice if a different communication protocol could be used to allow for more flexibility and interoperability.

Enter RMI over IIOP (RMI/IIOP). By using this protocol rather than JRMP, developers can write remote interfaces between clients and servers of different languages and vendors and implement them using only Java technology and the Java RMI APIs. The developer uses the RMI API and then takes advantage of the IIOP protocol to communicate with remote objects. It uses the best features of Java RMI and the Common Object Request Broker Architecture (CORBA) and helps speed application development by allowing the Java developer to work completely with the Java language.

Unlike CORBA, there is no Interface Definition Language (IDL) or mapping to learn for RMI over IIOP, so it's easier and faster to start developing than CORBA. Like Java RMI, RMI over IIOP allows developers to pass any serializable object to distributed components through pass- by-value methods. With RMI over IIOP, developers create Java interfaces and provide an implementation in other languages that support the OMG mapping and provide an Object Request Broker (ORB). Objects can be passed by value or by reference using RMI over IIOP. Figure 3.9 shows how RMI is used over the IIOP protocol.

Figure 3.9. You can use RMI on top of the IIOP protocol for better interoperability.

graphics/03fig09.gif

To round out our discussion of RMI, we'll provide a very basic example of using RMI over IIOP. We'll use the same example remote interface from Listing 3.1. Our new remote object will have to make some changes to accommodate the IIOP way of doing things. For our RMI implementation class, this means that instead of extending UnicastRemoteObject , it must extend javax.rmi.PortableRemoteObject . Listing 3.5 shows the small changes necessary to the implementation class.

Listing 3.5 The RMI Implementation Class for RMI/IIOP
 import java.rmi.RemoteException;  public class RMIUsingIIOPExampleImpl extends javax.rmi.PortableRemoteObject    implements RMIExample {     public RMIUsingIIOPExampleImpl() throws RemoteException  {       super();      }      public String getMessage() {       return "Hello from the RMI Server";      }  } 

The other required changes must be made in the server startup class that first creates an instance of the remote server object from Listing 3.5. The main difference there is that instead of using RMI's rebind method to bind an instance of the remote object to the RMI registry, you should use something like Java Naming and Directory Interface (JNDI) to bind an instance of the remote object to the JNDI tree. The last minor changes are required to the client that is looking up and invoking operations on the remote object. The following code fragment illustrates the code inside the client application:

 // Create a hashtable to store the jndi properties  Hashtable env = new Hashtable();  env.put("java.naming.factory.initial",          "com.sun.jndi.cosnaming.CNCtxFactory");  env.put("java.naming.provider.url", "iiop://<hostname>:900");  // Create an initial context  Context ic = new InitialContext(env);  RMIExample obj = (RMIExample)PortableRemoteObject.narrow(                  initialNamingContext.lookup("RemoteObject"),                   RMIExample.class);  // invoke the remote operation  String msg = obj.getMessage(); 

The main difference to pick up on between a RMI client that uses JRMP and one that uses IIOP is that you must use the static narrow method on the PortableRemoteObject before you attempt to cast the object returned to the remote interface type. The reason for this is that, with JRMP, you are assured that both the client and server are written in Java and you can simply use a Java cast. However, with IIOP the server might be a CORBA C++ component and you will need to narrow the object type to one of the proper class before you use Java's cast operator. With EJB applications where the client and server are both written in Java and the remote interface object is already an instance of the correct type, the narrow method might just return the object directly. However, you should always use the narrow method before using the Java cast operator.

Tip

Local EJB clients don't have to use the narrow method on the PortableRemoteObject . They are free to use the normal Java cast operator because the local client and the enterprise bean must be collocated within the same JVM.


This code fragment exposes some new information that is not covered until Chapter 4, "Java Naming and Directory Interface," so don't worry too much about trying to understand it. There will be plenty of time for that in Chapter 4.

You can get more information on RMI over IIOP at Sun's Web site at

http://java.sun.com/products/rmi-iiop



Special Edition Using Enterprise JavaBeans 2.0
Special Edition Using Enterprise JavaBeans 2.0
ISBN: 0789725673
EAN: 2147483647
Year: 2000
Pages: 223

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net