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.
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.
Tip
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.
Warning
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.
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 classAn
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.
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 | |
|
This option prints a description of all options available to
|
|
This option specifies the output folder for the generated files. |
|
This option prints out version information. |
|
This option allows you to specify the location of the Java compiler to use. |
|
This option specifies the classpath to be used during compilation. |
|
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. |
|
This option instructs the RMI compiler to create a descriptor for each remote class. |
|
This option indicates that all calls to the remote object will be
one-way calls (i.e., all methods of the remote interface return
|
|
This option keeps the generated source files after the RMI compiler has completed. |
Cluster options | |
|
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. |
|
This option specifies a custom load-balancing strategy. You can
choose from one of the following values:
|
|
This option specifies a custom call-routing class. A
|
Cluster options | |
|
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. |
|
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.
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.
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 betweenServerA
andServerB
because we invoked thermic
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 thermic
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 toServerA
.
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
.
Get WebLogic: The Definitive Guide now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.