MokaByte Numero 14 - Dicembre 1997 |
|||
|
Java Reflective Broker
|
||
di |
Specification | ||
Introduction
Java Reflective Broker (JRB - pronounced jerby) is a distributed
object model for the Java language that retains the semantics of the Java
object model, making distributed objects easy to implement and to use.
The system uses two important frameworks of the JDK1.1, namely Java Serialization
and Java Reflection.
Rationale
Distributed systems require that computations running in different
address spaces, potentially on different hosts, be able to communicate.
For a basic communication mechanism, the Java language supports sockets,
which require the client and server to engage in applications-level protocols
to encode and decode messages for exchange, and the design of such protocols
is cumbersome and error-prone.
An alternative to sockets are Object Request Brokers (ORBs) that abstract away the communication interface to the level of a remote method invocation. Instead of working directly with sockets, the programmer has the illusion of calling a local method, when in fact the arguments of the call are packaged up and shipped off to the remote target of the call. Traditional ORBs, such as CORBA and DCOM, are designed to work across languages, operating systems, and hardware platforms, thereby falling short of seamless integration with the specific features of a certain environment. CORBA users know that mapping IDL constructs into programming languages, such as C, C++, Smalltalk or Java, can be clumsy and unnatural.
The new Java Development Kit 1.1 supports Java RMI (Remote Method Invocation) which assumes the homogeneous environment of the Java Virtual Machine and can therefore take advantage of the Java object model whenever possible. Though Java RMI is far simpler than CORBA, some drawbacks still remain:
JRB Architecture
The architecture of the JRB essentially builds upon two primary classes,
the ObjectRef class and the Server class that represents
respectively the client and server side of the JRB architecture.
ObjectRef class
When writing an applet or an application that uses remote objects, the programmer needs to be aware of the JRB client visible interfaces. JRB client functions are provided by the cselt.jrb.ObjectRef class used to:
Server classpackage cselt.jrb;
import java.io.*;
import java.lang.reflect.*;
public class ObjectRef implements Serializable {
public static String getLoaderName() {...}
public static void setLoaderName(String name) {...}
public ObjectRef(URL url) {...}
public ObjectRef(String url) throws MalformedURLException {...}
public String getHost() {...}
public int getPort() {...}
public String getObjectName() {...}
public String toString() {...}
public Object invoke(String methodName, Object args[])
throws Exception {...}
}
The NetClassLoader classpackage cselt.jrb;
import java.io.*;
import java.lang.reflect.*;
public class Server {
public static void setPort(int port) throws Exception {...}
public static int getPort() {...}
public static ObjectRef export(Object obj) {...}
public static ObjectRef export(String objectName, Object obj) {...}
public static void retract(String objectName) {...}
public static void retract(ObjectRef ref) {...}
public static Object lookup(String objectName) {...}
}
The loadClass method loads the specified class name via the URL defined by the setURL method. The class is loaded, defined, and returned. The JRB runtime may create instances of this class to load classes of arguments received in remote invocations if such classes cannot be found locally by the default class loader. Server processes must declare the location of the classes (arguments/returns) by setting the URL, using the normal protocols, such as http and ftp, with the setURL method of the NetClassLoader class, e.g.package cselt.jrb;
import java.io.*;
import java.net.*;
public class NetClassLoader extends ClassLoader {
public static void setURL(URL url) {...}
public static void setURL(String url)
throws MalformedURLException {...}
public static URL getURL() {...}
public Class loadClass(String name)
throws ClassNotFoundException;
}
NetClassLoader.setURL("http://loom.cselt.it/~ennio/java/codebase/");
Although the JRB system provides the NetClassLoader class, the programmer is given the chance to customize the behaviour of class loading, for example by providing a class loader that loads class with an alternative mechanism to the URL-based model. All the programmer has to do is define a class loader, e.g. by inheriting from cselt.jrb.NetClassLoader or from java.lang.ClassLoader, and then let the JRB runtime know of this new class. For example, suppose your class loader is mypackage.MyClassLoader, then you simply call:
ObjectRef.setLoaderName("mypackage.MyClassLoader");
before any remote invocation is performed.
Naming class
For a client to be able to invoke a method
on a remote object, that client must first obtain an object reference.
Object references are created by exporting an object with the JRB runtime
system through the export
operation of the Server class. Most of the time the client will
obtain a reference to a remote object as a parameter to, or a return value
from, another remote method call. For bootstrapping, the client must be
able to obtain the first reference to a remote object. The simplest way
is for the server to listen on a well known port and export an object with
a name also known by the client. The client can use a URL-based naming
scheme of the form host:port/objectName to create an object reference, where objectName
is a simple string name used in the export
operation:
ObjectRef ref = new ObjectRef("loom.cselt.it:9999/MyObjectName");
If starting a server on a well known port is considered restraining, the JRB system provides a Naming Server. The cselt.jrb.Naming class provides methods for lookup, binding, rebinding, unbinding, and listing the contents of the Naming Server using a URL-based naming model. Being Naming a remote object, clients use the JRB to invoke the methods of the Naming class. This can be done either through the invoke method of the generic object reference ObjectRef, e.g.
ObjectRef.invoke("bind", "MyObjectName", objRef);
or through the method of the specialized object reference NamingRef generated by the stub generator, e.g.
NamingRef.bind("MyObjectName", objRef);
package cselt.jrb;
import java.io.*;
import java.net.*;
public class Naming {
public static int DEFAULT_PORT = 6666;
public ObjectRef lookup(String name)
throws IOException {...}
public void bind(String name, ObjectRef objRef)
throws IOException {...}
public void rebind(String name, ObjectRef objRef) {...}
public void unbind(String name)
throws IOException {...}
public String[] list() {...}
}
The compiler itself is written in Java and must be launched with:
java cselt.jrb.Stub <flags> <className>
<className> is the package qualified class name of the remote object class. The class must have previously been compiled successfully with javac. <flags> are all the flags applicable to the javac compiler plus the optional flag -package <packageName>. If this flag is given, the package name of the specialized object reference class will be <packageName>, otherwise the package will correspond to the remote object class’s package. This option is useful if you need to remote-enable a library class. For instance to remote-enable the Vector class of the java.util package you should not create a specialized object reference in java.util, rather, you may call:
java cselt.jrb.Stub -package examples.hello java.util.Vector
this creates a VectorRef class in
package examples.hello.
JRB through Firewalls
Many intranets have firewalls that do
not allow opening direct sockets to hosts on the Internet. Therefore JRB
uses http as the default transport layer to enable a client behind a firewall
to invoke a method on a remote object which resides outside the firewall.
The JRB transport layer embeds serialized objects in the body of an http
POST request and response. The JRB runtime on the target machine listens
with a server socket that is capable of understanding and decoding JRB
invocations inside http POST requests.
If the target object resides on a machine that is not visible by the client, JRB provides a proxy servlet to forward an invocation to the target JRB object. Servlets are the dynamic Java extensions to web servers as applets are to web browsers. Suppose that JRB object, say ObjectServer, listens on machine loom.cselt.it on port 9999 which is inside an intranet and is not visible by a remote client in the Internet. However, that intranet is connected with the Internet through a web server on machine andromeda.cselt.it. If the web server supports Java servlets, the client can indeed invoke ObjectServer by construing an object reference of the kind:
This URL is delivered to the web server listening on andromeda.cselt.it causing the dynamic loading and invocation of JRB's ProxyServlet servlet. This servlet uses the query argument to determine the target object and deliver the invocation to ObjectServer on loom.cselt.it. Objviously this approach only works if the web server supports the servlet extension. A similar approach, though more time consuming, can be realized using CGI scripts.ObjectRef ref = new ObjectRef("http://andromeda.cselt.it/servlet/
cselt.jrb.ProxyServlet?http://loom.cselt.it:9999/ObjectServer");
JRB Event Service
The standard JRB architecture supports
the synchronous, point-to-point, connection-oriented communication model.
In this model the client is explicitly aware of which target object it
is invoking. The JRB Event Service supports an asynchronous, multicast,
connection-less communication model where a sender object, the talker,
sends a message and is unaware of the identity and number of recipients,
the listeners, of the message. Consequently any messages is uni-directional,
from one or more talkers to one or more listeners. In general, M talkers
can issue messages via the same message stream to N listeners, without
any of the talkers and listeners having explicit knowledge of each other.
An advantage of this approach is that new talkers and listeners can be
added easily.
Topic names
In Java Beans event model, listener objects
interested in certain events must know the identities of the source objects
of those events and register directly with them, which, in turn, have to
maintain a list of the interested listeners to forward the events. The
JRB Event Service follows the standard Java Beans event model but now listeners
do not have to know the identity of the talkers and talkers do not have
to maintain a list of listeners to forward the events. Events are identified
by topic names, and listeners specify events of interest by informing
the Event Service of topics interest to them. Topic names are organized
into a hierarchical tree structure, like a file system. When a talker sends
an event, the topic name of the event identifies a branch of a tree, e.g. "/cselt/teleconf/10:00am".
All listeners that have registered for topic names along this branch will
be notified of the event. In the example listeners that have registered
for: "", "/cselt", "/cselt/teleconf", "/cselt/teleconf/10:00am"
are all notified. The further down the tree hierarchy, the more specific
the event registration becomes.
Talker class
The Talker class provides support
for sending either singlecast or multicast events depending on the IP address
specified in the constructor. If the address denotes an IP mulitcast group
(Class D Internet address in range 224.0.0.0 to 239.255.255.255), all listeners
that have joined that multicast group will receive the event, possibly
on several different Virtual Machines. If, on the other hand, the address
identifies a certain host-name/port-number, the talker will send the event
only to the listeners in the Virtual Machine identified by that address.
The cselt.jrb.Talker
class is used to:
Due to browsers' security policies, applets are not usually allowed to use IP multicast addresses. However, an applet is allowed to listen to events sent by the host whence the applet was downloaded.package cselt.jrb;
import java.io.*;
public class Talker implements Serializable {
public Talker() {...}
public Talker(String group) {...}
public Talker(String group, int port) {...}
public String getAddress() {...}
public int getPort() {...}
public String toString() {...}
public void send(String topicName, Object arg)
throws Exception {...}
}
Listener interface
Listeners must implement the cselt.jrb.Listener
interface. This interface provides a single operation, receive,
which is invoked to notify the listener of the occurrence of the event.
ProxyListener classpackage cselt.jrb;
import java.io.*;
public interface Listener extend java.util.EventListener {
public void receive(String topicName, Object arg);
}
JRB and RMI comparisonpackage cselt.jrb;
import java.io.*;
public class ProxyListener {
public static int packetSize = 4096;
public static boolean multiThread = true;
public static void setMulticastAddress() {...}
public static void setMulticastAddress(String address)
throws Exception {...}
public String getAddress();
public static void setPort(int port) throws Exception {...}
public static int getPort() {...}
public static void addListener(String topicName, Listener l) {...}
public static void removeListener(String topicName, Listener l) {...}
}
Remote-enabling a class
With RMI, the following steps are required
to enable an existing class for remote method invocation:
With JRB, to enable an existing class for remote method invocation, simply export an instance of the class with the export method of the Server class. You do not have to modify the original class in any way, not even access to its source code. This means that you can effortlessly enable any third-party library for remote method invocation. For example, to remote enable an instance of the java.util.Vector class simply export it:
...
Vector v = new Vector();
Server.export(v);
Exporting a Named Remote Object
Both RMI and JRB provides a Naming Server,
called registry in RMI, for binding an object to a name, then retrieving
the object via its name. To create an object in a server and then export
the object for use by remote clients, the following steps are required:
Server.setPort(9999);
Server.export("MyVector", new Vector());
then clients can bind the remote object by creating an object reference:
VectorRef vector = new VectorRef("loom.cselt.it:9999/MyVector");
Garbage Collection
RMI has a lease-based garbage collection
system. When a client obtains a reference to a remote object, the client
is granted a lease that must be periodically renewed in order to prevent
the remote object from being garbage collected. A remote object is garbage
collected when all of its leases have expired. This garbage collection
mechanism may severely affect the performance of the application due to
the considerable overhead on the wire. Instead of relying on a distributed
garbage collection mechanism, in JRB servers maintain control on the exported
objects. Objects are not garbage collected till they are explicitly retracted.
Product Size
RMI's total class file size is 175KBs
uncompressed. This includes all primary RMI .class files, the registry,
and the distributed garbage collection system but excludes the http firewall
support. JRB's total class file size is less than 20KBs uncompressed, an
order of magnitude less than RMI!
Stubs and Skeletons
RMI uses the rmic
utility to generate stubs and skeletons for remote method invocation. If
using the dynamic invocation model, JRB doesn’t need stubs and skeleton.
If the programmer prefers to use a static invocation model JRB provides
a stub generator. This section shows the code that each system generates
for the twoInts
method of the following interface:
public interface MyInterface extends Remote
{
public int twoInts(int a, int b) throws RemoteException;
}
As the code examples below demonstrate, JRB typically emits 90% less support code than RMI. The stub code for twoInts with RMI:
The skeleton code for twoInts with RMI:public int twoInts(int $_int_1, int $_int_2) throws java.rmi.RemoteException {
int opnum = 1;
java.rmi.server.RemoteRef sub = ref;
java.rmi.server.RemoteCall call =
sub.newCall((java.rmi.server.RemoteObject)this, operations,
opnum, interfaceHash);
try {
java.io.ObjectOutput out = call.getOutputStream();
out.writeInt($_int_1);
out.writeInt($_int_2);
} catch (java.io.IOException ex) {
throw new java.rmi.MarshalException("Error marshaling arguments", ex); };
try {
sub.invoke(call);
} catch (java.rmi.RemoteException ex) {
throw ex;
} catch (java.lang.Exception ex) {
throw new java.rmi.UnexpectedException("Unexpected exception", ex);
};
int $result;
try {
java.io.ObjectInput in = call.getInputStream();
$result = in.readInt();
} catch (java.io.IOException ex) {
throw new java.rmi.UnmarshalException("Error unmarshaling return", ex);
} catch (Exception ex) {
throw new java.rmi.UnexpectedException("Unexpected exception", ex);
} finally {
sub.done(call);
}
return $result;
}
The stub code for twoInts with JRB:case 0: { // twoInts
int $_int_1;
int $_int_2;
try {
java.io.ObjectInput in = call.getInputStream();
$_int_1 = in.readInt();
$_int_2 = in.readInt();
} catch (java.io.IOException ex) {
throw new java.rmi.UnmarshalException("Error unmarshaling arguments", ex);
} finally {
call.releaseInputStream();
};
int $result = server.twoInts($_int_1, $_int_2);
try {
java.io.ObjectOutput out = call.getResultStream(true);
out.writeInt($result);
} catch (java.io.IOException ex) {
throw new java.rmi.MarshalException("Error marshaling return", ex);\
};
break;
}
There is no skeleton code because JRB does not require skeleton code on the server side.public int twoInts(int p1, int p2) throws Exception {
Object o[] = {new Integer(p1), new Integer(p2)};
return ((Integer)invoke("twoInts", o)).intValue();
}
Ennio Grasso appartiene alla linea di Tecnologie Softeware di
CSELT. Occupandosi di object-oriented distributed computing da quando è
entrato in CSELT nel 1992, ha maturato una certa esperienza sia progettuale
che implementativa.
È rappresentante CSELT in OMG e segue lo standard CORBA sin dalle prime fasi. Autore del modello transazionale dell'architettura TINA, ha studiato l'applicazione di concetti transazionali rilassati per sistemi distribuiti dove il concetto di "transazionalità" deve essere adattato al dominio applicativo. Ultimamente la sua area di competenza si è concentrata sugli aspetti architetturali di Java, in particolare per gli aspetti di distrubuted computing, migrazione di codice e mobile agents. |
|
||
|
||
MokaByte
rivista web su Java |
||
|