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.

If you would like to use the WebLogic RMI counterpart to java.rmi.Remote, you could use the weblogic.rmi.Remote interface instead. If you do, we recommend that you do not mix the standard RMI classes and interfaces with their WebLogic RMI counterparts (use either all WebLogic RMI classes or all Sun-standard RMI classes).

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.

WebLogic's RMI will not dynamically load classes delivered from an external client. Thus, you must ensure all RMI interfaces and implementation classes (and any classes needed by them) are available under the server's classpath.

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:

  1. Write and compile the startup class.
  2. Make the startup class available to the server's classpath.
  3. Register the startup class via the Administration Console.
  4. 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:

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.

Table 4-2. General and cluster options available to WebLogic's RMI compiler

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.

You cannot bind a nonclusterable RMI object to the server's JNDI tree with REPLICATE_BINDINGS set to true. In a cluster, only one server may host a nonclusterable RMI object.

 

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:

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.

Категории