Using WebLogics RMI
Using WebLogic s RMI
RMI is the standard model for distributed object programming in Java. Using RMI, a Java client can access a remote object seamlessly on another JVM, and invoke methods on the object as if it were located within the client's JVM. In addition, RMI incorporates various reference semantics for remote objects, such as lazy activation, live (or nonpersistent) references, and persistent references.
WebLogic RMI is an integral part of the server framework. It enables a Java client to transparently access RMI objects that live on WebLogic Server. This includes access to any EJB components and other J2EE resources that have been deployed to WebLogic. It allows you to build fast, reliable, standards-compliant RMI applications. It also incorporates support for load balancing and failover when RMI objects are deployed to a WebLogic cluster. WebLogic RMI is fully compatible with the RMI specification, but provides extensions not available under the standard RMI implementation. Here's a brief overview of some of the extra benefits of using WebLogic's version of RMI.
Performance and scalability
WebLogic incorporates a highly optimized implementation of RMI. It handles all the implementation issues that relate to the support for RMI: managing threads and sockets, garbage collection, and serialization. Standard RMI relies on separate socket connections between the client and the server, and between the client and the RMI Registry. WebLogic RMI multiplexes all this network traffic onto a single socket connection between the client and the server. The same socket connection is reused for other kinds of J2EE interaction as well, such as JDBC requests and JMS connections. By minimizing the network connections between the client and WebLogic, the RMI implementation is able to scale well under load, and support a large number of RMI clients simultaneously. It also relies on high-performance serialization logic. All these features mean a significant performance gain in client-server communication.
In addition, WebLogic automatically optimizes client-server interactions when the client runs within the same VM as the RMI object. It ensures that you don't incur any performance penalty because of marshalling or unmarshalling of arguments during a call to a remote method. Instead, WebLogic uses Java's pass-by-reference semantics when the client and the server object are collocated, and when the class-loader hierarchy permits it.
Interclient communication
WebLogic's RMI provides asynchronous, bidirectional socket connections between the client and the server. An RMI client can invoke methods exposed by server-side RMI objects, and by other client-side RMI objects that have registered their remote interfaces over WebLogic's RMI Registry. Thus, a client application can publish RMI objects through the server registry, and other clients or servers can use these client-resident objects just as they would use any server-resident objects. In this way, you can create applications that involve peer-to-peer, bidirectional communication between RMI clients.
RMI Registry
The RMI Registry runs automatically whenever WebLogic is started. WebLogic ignores attempts to create multiple instances of the RMI Registry, and simply returns a reference to the existing registry.
WebLogic's RMI Registry is fully integrated with the JNDI framework. You may use either the JNDI or the RMI Registry to bind or look up server-side RMI objects. In fact, the RMI Registry is merely a thin façade over WebLogic's JNDI tree. We recommend that you directly use the JNDI API for registering and naming RMI objects, bypassing calls to the RMI Registry altogether. JNDI offers the prospect of publishing RMI objects over other enterprise naming and directory services, such as LDAP.
Tunneling
RMI clients can use URLs based on a variety of schemes: the standard rmi:// scheme, or the http:// and iiop:// schemes that tunnel RMI requests over HTTP and IIOP, respectively. This enables RMI calls from the client to penetrate through most firewalls.
Dynamic generation of stubs and skeletons
WebLogic supports dynamic generation of client-side stubs and server-side skeletons, eliminating the need to generate client-side stubs and server-side skeletons for the RMI object. WebLogic will automatically generate the necessary stubs and skeletons when the object is deployed to the RMI Registry or JNDI. The only time you need to explicitly create stubs is when the server-side RMI object needs to be accessed by either clusterable or IIOP clients.
4.3.1 Programming Issues
Let's now look at some of the important ways in which WebLogic RMI allows you to depart from the standard RMI programming model. We'll use the example of a simple server-side object that returns the sum of two incoming arguments. Here's an example of a remote interface that exposes a single method, add( ):
package com.oreilly.rmi; public interface Add extends java.rmi.Remote { /* Returns the sum */ int add(int a, int b) throws java.rmi.RemoteException; }
Here, java.rmi.Remote represents the marker interface that all RMI objects are required to implement.
|
Now, we need to provide an implementation for this interface. Example 4-3 shows a simple implementation for the Add interface.
Example 4-3. Implementation class for the com.oreilly.rmi.Add interface
package com.oreilly.rmi; public class AddImpl implements Add, java.io.Serializable { // With WebLogic RMI, the implementation class doesn't need to extend // java.rmi.server.UnicastRemoteObject public AddImpl( ) throws java.rmi.RemoteException { super( ); } /* Implements the remote method signature */ public int add(int a, int b) throws java.rmi.RemoteException { return a+b; } }
The AddImpl class provides the implementation for the add( ) method exposed by the remote interface. WebLogic doesn't require that the implementation class for the RMI object extend UnicastRemoteObject. Instead, it is the WebLogic RMI base class stub that mimics the java.rmi.server.UnicastRemoteObject class. In this way, the implementation class can inherit any application-specific Java class and still continue to function like a server-side RMI object. It also doesn't require you to explicitly declare that each remote method throws a java.rmi.RemoteException. Your remote interface also may declare method signatures that rely on application-specific exceptions, including any subclasses of java.lang.RuntimeException. This means you won't need to change any exception-handling code for your existing RMI implementation classes. Both of these extensions add convenience, but you must remember that the resulting code will be less portable if you use them.
|
Publishing the RMI object is equally easy, as shown in Example 4-4.
Example 4-4. Publishing an RMI object
package com.oreilly.rmi; import javax.naming.*; import java.util.Hashtable; public class AddBind { // The factory to use when creating our initial context public final static String JNDI_FACTORY="weblogic.jndi.WLInitialContextFactory"; /** * Create an instance of the Implementation class * and bind it in the registry. */ public static void main(String args[]) { // Creating and installing the SecurityManager is not required if (System.getSecurityManager( ) == null) System.setSecurityManager(new weblogic.rmi.RMISecurityManager( )); try { Context ctx = getInitialContext("t3://localhost:7001"); ctx.bind("AddServer", new AddImpl( )); System.out.println("AddImpl created and bound to the JNDI"); } catch (Exception e) { System.out.println("AddImpl.main: an exception occurred!"); e.printStackTrace(System.out); } } /* Creates the Initial JNDI Context */ private static InitialContext getInitialContext(String url) throws NamingException { Hashtable env = new Hashtable( ); env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY); env.put(Context.PROVIDER_URL, url); return new InitialContext(env); } }
The main( ) method is responsible for creating an instance of the implementation class and then publishing it to the RMI Registry. In this case, we've bound the server object to the name AddServer. Notice how WebLogic's RMI supports naming and lookup via JNDI. Alternatively, we could have used the java.rmi.Naming class to bind and look up objects published over the server registry.
WebLogic doesn't require you to assign a SecurityManager in order to incorporate security into the RMI object. Instead, WebLogic relies on a richer security framework covered in Chapter 17. WebLogic RMI supports the setSecurityManager( ) method for portability reasons only.
Once you have compiled the server classes and published the RMI object over the registry, you then can create an RMI client that looks up the object in the registry and invokes the remote methods exposed by its remote interface. Example 4-5 shows how to invoke the Add RMI object we published earlier.
Example 4-5. Invoking the published RMI object
package com.oreilly.rmi; import javax.naming.*; import java.util.Hashtable; public class AddClient { public static void main(String[] argv) throws Exception { try { InitialContext ic = getInitialContext("t3://localhost:7001"); Add obj = (Add) ic.lookup("AddServer"); System.out.println("Successfully connected to AddServer " + obj.add(3,4) ); } catch (Throwable t) { t.printStackTrace( ); } } /* Creates the Initial JNDI Context. */ private static InitialContext getInitialContext(String url) throws NamingException { Hashtable env = new Hashtable( ); env.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory"; env.put(Context.PROVIDER_URL, url); return new InitialContext(env); } }
If this client runs within the same server JVM that hosts the RMI object, you effectively bypass the need for marshalling and unmarshalling of method arguments and the return value. Instead of pass-by-value, WebLogic RMI automatically ensures that the "remote" method call defaults to standard Java pass-by-reference semantics. This will be subject to classloader concerns. Chapter 12 provides a detailed look at the interaction of the classloader and application packaging and how it can affect this RMI optimization.
4.3.2 Using a WebLogic Startup Class to Register an RMI Object
Earlier we wrote a client application that was responsible for registering an instance of the remote object with the RMI Registry. If you want to always make a remote object available to the server instance, you should consider writing a WebLogic startup class that performs the same task. Startup classes are a proprietary feature in WebLogic Server. WebLogic invokes all startup classes targeted to it as part of its runtime initialization. Creating a startup class is a simple procedure:
- Write and compile the startup class.
- Make the startup class available to the server's classpath.
- Register the startup class via the Administration Console.
- Restart the server.
Example 4-6 lists the code for a startup class that binds an RMI object to the server's JNDI tree.
Example 4-6. Startup class for registering custom RMI objects
public class InitRMIObjects implements T3StartupDef { private T3ServicesDef services; //Defines the JNDI context factory public final static String JNDI_FACTORY= "weblogic.jndi.WLInitialContextFactory"; /** * A T3StartupDef callback method */ public void setServices(T3ServicesDef services) { this.services = services; } /** * A T3StartupDef callback method * Registers the RMI Object to the JNDI tree */ public String startup(String name, Hashtable args) throws Exception { String foo = (String) args.get("foo"); try { Context ctx = new InitialContext( ); ctx.bind("AddServer", new AddImpl( )); System.out.println("AddImpl created and bound in the registry to the name AddServer"); } catch (Exception e) { System.out.println("AddImpl.error: " + e.printStackTrace( )); } return ""; } }
WebLogic requires that each startup class implement the two callback methods of the weblogic.common.T3StartupDef interface. The T3ServicesDef object lets you access proprietary WebLogic resources. For the most part, the setServices( ) method can remain empty.
The startup( ) method implements the actual task that needs to be performed during server bootstrap. WebLogic invokes the startup( ) method with two parameters:
- A name parameter that represents the registered name for the startup class
- An args parameter that represents a hash map of arguments and key/value pairs that are specified when you register the startup class using the Administration Console
Once you've compiled the startup class and made it available to the server's classpath, you need to register it with a particular server instance. Choose the Deployments/Startup and Shutdown node from the left pane of the Administration Console, and then select the Configure a New Startup Class link from the right pane. You must then specify a name and the fully qualified class name of the startup class. You also can specify arguments to the startup class using a comma-separated list of key/value pairs:
propA=value,propB=value,...
If you select the "Failure is Fatal" option, WebLogic aborts its initial bootstrap if the startup class fails for whatever reason. Finally, you can choose when the startup class should be executed, with respect to the startup process of WebLogic and the applications deployed to it. The default is to execute the startup class after applications are deployed. If you choose the "Run before application deployments" option, the startup class will run before any services have been initialized or applications deployed. See Chapter 12 for a detailed explanation of these states.
Once you've saved these configuration settings, you must target the startup class to a WebLogic Server instance. After this, you can restart the server and you'll find an RMI object bound to the server's JNDI tree under the name AddServer as soon as server initialization has completed.
Similarly, WebLogic lets you create shutdown classes that encapsulate various tasks that must be executed automatically when the server shuts down. You can target a number of startup and shutdown classes to a server, or across all members of a WebLogic cluster.
4.3.3 WebLogic's RMI Compiler
Many RMI compilers generate proxies for server-side RMI objects. Method calls from the client are routed via the proxy to the server-side object. The proxy class is responsible for marshalling and unmarshalling method parameters and return values of the method call. WebLogic's RMI compiler relies on dynamically generated client-side stubs and server-side skeletons instead, so you don't need any additional classes bundled with the RMI object. The only reason you need to explicitly invoke the RMI compiler is when the remote object needs to serve IIOP clients, or when the RMI object needs to be deployed to a cluster.
Here's how you can invoke WebLogic's RMI compiler:
java weblogic.rmic [options] rmi-class-name ...
The RMI compiler will accept any option supported by the Java compiler. For example, -d /serverclasses instructs the RMI compiler to place generated files in the /serverclasses folder. Table 4-2 lists the various options available to the RMI compiler for general and cluster usage.
Option |
Description |
---|---|
General options |
|
-help |
This option prints a description of all options available to weblogic.rmic. |
-d |
This option specifies the output folder for the generated files. |
-version |
This option prints out version information. |
-compiler |
This option allows you to specify the location of the Java compiler to use. |
-classpath |
This option specifies the classpath to be used during compilation. |
-nontransactional |
This option indicates that the RMI object must not participate in a transaction. If a transaction exists, it must be suspended before the remote method is invoked, and later resumed when the method call completes. |
-descriptor |
This option instructs the RMI compiler to create a descriptor for each remote class. |
-oneway |
This option indicates that all calls to the remote object will be one-way calls (i.e., all methods of the remote interface return void). |
-keepgenerated |
This option keeps the generated source files after the RMI compiler has completed. |
Cluster options |
|
-clusterable |
This option instructs the compiler to mark the remote object as being clusterable. This means the RMI object can be hosted by multiple servers in a WebLogic cluster. Each replica of the RMI object will be bound under the same JNDI name. Any client that looks up the JNDI name will obtain a cluster-aware RMI stub that maintains a list of all replicas of the remote object. This allows the stub to perform failover and load balancing among available replicas of the RMI object. |
-loadAlgorithm |
This option specifies a custom load-balancing strategy. You can choose from one of the following values: round-robin, random, or weight-based. This option may be used if the RMI object is marked as clusterable. |
-callRouter |
This option specifies a custom call-routing class. A CallRouter class is invoked before each method call to the RMI object. It determines which one of the servers hosting the RMI object should be chosen, based on the method parameters supplied. If it returns null, the current load-balancing strategy is used. Again, this option may be used only if the RMI object has been marked as clusterable. |
Cluster options |
|
-methodsAreIdempotent |
This option indicates that all methods of the RMI object are idempotent. This way, a cluster-aware stub can retry the method call if the previous attempt failed. By default, a cluster-aware stub will retry the method call only if the failure occurred before the previous attempt. You may use this option provided the RMI object also has been marked as clusterable. |
-stickToFirstServer |
This option indicates that the stub ought to be constrained by "sticky" load balancing i.e., the cluster member chosen to service the first request is then used for all subsequent requests from the same client. Again, this option may be used only alongside the clusterable option. |
When you run the RMI compiler with the -clusterable option, it generates an XML descriptor file that captures all cluster options that apply to the RMI object. For instance, the RMI compiler generates an AddImplRTD.xml descriptor file if you invoke it using one or more cluster options:
java weblogic.rmic -clusterable -loadAlgorithm round-robin -methodsAreIdempotent com.oreilly.rmi.AddImpl
The XML descriptor file must then be made available to the server's CLASSPATH, and WebLogic will ensure the RMI stubs for the remote object adhere to the cluster options captured in the descriptor file.
4.3.4 RMI Objects in a Cluster
The behavior of RMI objects in a cluster depends on whether the object is made clusterable, and on the value of the REPLICATE_BINDINGS property when the object is bound in the cluster. Let's look at these scenarios in more detail.
Consider a WebLogic cluster that consists of three servers (A, B, C). Suppose you register a clusterable RMI object to Servers A and B. Now, if the RMI implementation class was bound to the server's JNDI tree with REPLICATE_BINDINGS enabled, the RMI stub will be available on all three servers A, B, and C. When a client executes a JNDI lookup, the RMI stub can be obtained from all three servers. However, method calls to the remote object will be load-balanced between Servers A and B only, according to the load-balancing strategy chosen during RMI compilation.
Suppose you deploy the clusterable RMI object to Servers A and B, but this time with REPLICATE_BINDINGS set to false. This means that if the client performs a JNDI lookup on Server A, it obtains a pinned RMI stub that refers to the remote object on Server A only. Similarly, if the client executes a JNDI lookup on Server B, the RMI stub it gets refers to the remote object on Server B. Of course, because the bindings were not replicated, the object will not be made available to Server C, and any JNDI lookup on Server C will fail with a NameNotFoundException error. In this situation, suppose the client obtains the RMI stub from Server A, and Server A fails before the client is able to invoke one of the remote methods. Because the RMI stub is clusterable, it will do another JNDI lookup for the RMI stub from the remaining servers in the cluster that are still alive. The method call will succeed if Server B is chosen on the next attempt, and will fail with a NameNotFoundException error if the request is routed to Server C.
Now suppose you bind a nonclusterable RMI object to the server's JNDI tree with REPLICATE_BINDINGS set to false. This means that when a client performs a JNDI lookup on Server A, it obtains an RMI stub that points to the object on Server A. The same holds true for any other servers to which the RMI object has been deployed. No failover is possible in this scenario; if a remote method call fails, it will not be redirected to any of the other servers in the cluster.
|
4.3.5 Cluster Example
It is a simple matter to put this theory into practice! Let's create a clusterable version of the Add RMI object created earlier, bind it to a number of servers in a cluster, and access it from a client.
The first thing you want to do is create a clusterable version of the RMI object. This is done easily with the rmic compiler, where we have specified that we want a clusterable object with round-robin load balancing:
java weblogic.rmic -clusterable -loadAlgorithm round-robin -methodsAreIdempotent com.oreilly.rmi.AddImpl
This just creates an XML file, AddImplRTD.xml, which you should place in the server's CLASSPATH along with the Add class and implementation. You can publish the object to JNDI by binding an instance of the object to a server. However, because the object is clusterable and we want to test these features, we bind it to two servers in a cluster:
Context ctx = getInitialContext("t3://ServerA:7001"); ctx.bind("AddServer", new AddImpl( )); ctx.close( ); ctx = getInitialContext("t3://ServerB:7001"); ctx.bind("AddServer", new AddImpl( )); ctx.close( );
According to the rules given in the previous sections, a single clusterable RMI stub will be bound in each JNDI tree in the cluster. Moreover, the RMI stub will record the location of each server hosting the actual object in this case, ServerA and ServerB. Using the clustered RMI object is no different from using any other RMI object. The following code creates a context, performs a lookup, and invokes the add( ) method on the object a number of times:
Context ctx = getInitialContext("t3://ServerA,ServerB:7001"); obj = (Add) ic.lookup("AddServer"); for (int i=0; i<10; i++) System.out.println("Calling add: " + obj.add(2,i);
Note the following about the behavior of this code, illustrating the powerful features of WebLogic's RMI:
- Each invocation of add( ) in the loop will alternate between ServerA and ServerB because we invoked the rmic compiler with the round-robin load-balancing argument. This load balancing is transparent, and obviously simple to use. Choosing a different load-balancing scheme is just a matter of passing different arguments to the rmic compiler.
- The RMI stub that we are using is clusterable, and hence can tolerate failover. For instance, we can take ServerB down during the loop without any ill effect all calls will just be routed to ServerA.
4.3.6 Using a Custom CallRouter Class with an RMI Object
WebLogic supports parameter-based load balancing using custom call-routing classes. The RMI compiler lets you associate a custom call-routing class with a clusterable RMI object. The class is invoked before each method call to the RMI object, allowing you to programmatically determine which server hosting the RMI object should be chosen, based on the method parameters in the call.
A custom call router for an RMI object must implement the weblogic.rmi.cluster.CallRouter interface. The interface method getServerList( ) will be called before each invocation of an RMI method and must return a list of server names. The call then will be routed to the first server in the list, or to others in the list if the first server has failed. If getServerList( ) returns null, the default load-balancing strategy for the stub is used. The getServerList( ) method is passed the method that is being called, as well as the parameters to that method; you can use any of this data in the decision-making process.
Let's modify our running example by creating a custom call router, which directs the addition of all large numbers (where we define "large" as the first parameter being greater than 5) to ServerA, and all smaller numbers to ServerB. Example 4-7 lists the code for our call-router class.
Example 4-7. A custom call router
public class NumberRouter implements weblogic.rmi.cluster.CallRouter { private static final String[] bigServers = { "ServerA" }; private static final String[] smallServers = { "ServerB" }; public String[] getServerList(Method m, Object[] params) { if ( ((Integer)params[0]).intValue( ) > 5) return bigServers; else return smallServers; } }
Once you've compiled the call router, you can then register it with the RMI object using WebLogic's RMI compiler:
java weblogic.rmic -clusterable -loadAlgorithm round-robin -callRouter com.oreilly.rmi.NumberRouter com.oreilly.rmi.AddImpl
Deployment proceeds in the same way as before, except you must additionally include the NumberRouter class file in your server's CLASSPATH.