Application of Dynamic Proxy in Java RMI
Forest Hou
September 6, 2000, Rickard Oberg (one of JBoss's main author) sticked a post to "HOWTO: Use Dynamic PROXIES AS RMI Stubs HOWTO: EXPORT DYNAMMIC PROXIES AS RMI Objects" in RMI-User Mail Group , How to use Dynamic Proxy in Java RMI. With this technology, programmers can add various services (Authentication, Logging, Transaction, etc.) without changing the RMI Interface. This technology is the key to JBoss implementation EJB model. This article details this technology in detail. Rickard has shown this technology with "SmartWorld" and placed on his website for download. Unfortunately, we can't see SmallWorld today. The author dollen the example in the rickard post, called "SmallWorld", explained in this article. The reader can download this example.
Add Interceptor in RMI
I really like Java RMI. RMI is very light relative to other distributed object models (CORBA, COM), and it is very simple. However, sometimes the programmer wants to add a variety of services in RMI without changing its Interface, but not that simple. "Interceptor" as an important "Design Pattern" is very popular in modern software technology, which is usually used to implement service, so it is one of the key technologies to implement Framework.
Here is a very simple RMI example. I want to join Interceptor on this example to show magical Dynamic Proxy technology.
//: SmallWorld Interface
Public Interface SmallWorld Extends Java.rmi.Remote
{
Public static final string name = "smallworld";
Public String Hello (INT i) throws java.rmi.remoteexception;
}
SmallWorld is a simple RMI Interface, which defines a method Hello ().
//: SmallWorldImpl
Import java.rmi.server. *;
Import java.rmi. *;
Public Class SmallWorldImpl Extends UnicastRemoteObject Implements SmallWorld {Public SmallWorldImpl () THROWS RemoteException {Super ();
Public String Hello (INT I) throws java.rmi.RemoteException {system.out.println ("in smallworldimpl i =" i); return ("Hello Number =" i);}}
SmallWorldImpl is the implementation of the SmallWorld Interface. This is a re-standard RMI implementation, you can find similar examples in any RMI textbook.
The implementation of the Client is as follows:
//: Client
Import java.rmi. *;
Public class test {public static void main (string [] args) {try {smallworld he = (smallworld) Naming.lookup (smallworld.name); system.out.println (He.hello (4)); system.out. Println (HE.HELLO (8));} catch (Exception E) {E.PrintStackTrace ();}}} Client First use RMI Naming Service to find SMALLWORLD STUB object, and then call its remote method "Hello ()".
Finally, it is a simple server that implements an instance of a SmallWorldImpl, then put it on the RMI Naming Service.
//: SIMPLESERVER
Import java.rmi. *;
Import java.rmi.server. *;
Import Java.rmi.Registry. *;
Public class simpleserver {public static void main (string [] args) {Try {// create remote object smallworld server = new smallworldIMPL ();
Registryreg = locateregistry.createRegistry (1099); reg.bind (smallworld.name, server);} catch (exception e) {E.PrintStackTrace ();}}}
Most RMI applications are implemented on the above model. However, this model is difficult to join Security, Log and other services. Because the remote objects and remote quantities in a formal RMI application are very large, and often in change, it is unrealistic to add a number of services (service) to each method. A project that I have participated in the author has a very good idea: the system wants to implement session management, so the Session Key is introduced. The problem is that each remote call must pass the session key, and each Method has to check the session key, I believe that there is no programmer to do this. As a result, it is of course you want to know.
In fact, every good Framework will use the following implementation:
Here Interceptors is used to implement various insertable services, such as Authentication, Access Controller, Logging, Performance Metrics, Transaction, and more. The key to this solution is that the various service is configurable, they are relatively independent of the remote approach. Therefore, the programmer written by the remote object does not have to insert a variety of service calls from time to time, because these services have been executed before entering the remote object.
For RMI, we asked the programmer's code without changing or rarely changing, and Service Provider can join a variety of interceptor. This is a very wonderful dream, and Rickard Oberg has finally realized this dream. Nowadays, this technology has become one of the two core technologies of JBoss (another core technology is JMX). To understand the technology, you have to go deep into the implementation of RMI, Java Dynamic Proxy, ClassLoader, and Object Serialization. Here I just speak these technologies in RMI Stub, if the reader feels confusion or wants to penetrate these technologies, see the references listed herein. Rickard Oberg almost every line code is worth careful, the author just started to see more, I don't understand, I don't understand, please understand the relevant information, this is to understand the truth. :-) Deeper Java RMI: RemoteObject, Remotestub, Remoteref, ExportObject is actually difficult to understand the RMI is very difficult. Just take a look at how many Class, Interface, Exception, you will understand. In contrast, EJB contains less things, and the relationship between Interface is also very simple. I am now interested in being: the remote object written by the programmer (SmallWorldImpl, is different from the RemoteObject Class, below I "Remote Object" means this level of Class, and "RemoteObject" indicates where the Class in Java RMI) is implemented? Several part? How does RMI create them?
Most remote object implementations have inherited UnicastRemoteObject. After generating a Class file with the Javac, compile the Class file with RMIC to generate the corresponding Stub and Skeleton Class. Stub class is the remote agent of the remote object, used to issue remote calls; Skeleton and remote objects are in the same address, Skeleton is used to receive, forward "call" to the object.
But Skeleton is not required. If you add "-v1.2" option with RMIC, generate the Java2 standard Stub Class, there is no Skeleton Class.
The key part is represented by the following UML chart:
Remoteref is RemoteObject's remote Handle, which is embodied in RemoteStub. The remote call of the Client end is implemented by Remoteref. You can generate the source code for SmallWorldImpl_stub with the following command:
RMIC-Keep -V1.2 SmallWorldImpl
among them
The Hello () method is implemented:
//Mplementation of Hello (int)
Public Java.lang.String Hello (int $ param_int_1) throws java.rmi.RemoteException
{
Try {
Object $ result = ref.invoke (this, $ method_hello_0,
New java.lang.Object [] {new java.lang.integer ($ param_int_1)},
--7781462318672951424L);
Return (Java.lang.String) $ Result);
} catch (java.lang.RuntimeException e) {
Throw e;
} catch (java.rmi.RemoteException e) {throw e;
} catch (java.lang.exception e) {
Throw new java.rmi.unexpectedException ("undeclared checked exception", e);
}
}
The prototype of Remoteref is:
Public Object Invoke (Remote Obj, Method Method, Object [] Params, Long OPNUM) THROWS Exception
Where opnum is a Hash value that represents Method, it is used as an Authentication, which we don't care here. We will see the reflective reflection of this method, making the interceptor of the implementation of the Client side.
Let's take a look at the generation process of Stub on the Server side, and the client discovered its process.
The SMALLWORLDIMPL constructor calls the super () method, which actually calls the construction method of UnicastRemoteObject, and the construction method of UnicastRemoteObject will call the exportObject () method. ExportObject is trying to load the SmallWorldImpl_stub classLoader's ClassLoad to generate it, which is:
// Assume Obj = SmallWorldImpl
String name = obj.getClass (). Getname ();
Class C = Class.Forname (Name "_stub", false, obj.getclass (). GetClassLoader ());
Remotestub stub = (remoteestub) c.newinstance ();
Then publish it to the specified socket, then the remote object can be used. For the Client side find convenient, Server should publish this Stub instance with Naming Service, which is the role of registry.bind ().
Java.rmi.registry.registry
Public void bind (String name, remote object);
This method sends the smallworldimpl_stub instance (serialization), and then sends the serial Object to the Naming Server.
Server can also directly call the unicastremoteObject () method to publish the remote object.
Client End Calls the registry.lookup () method to find the instance of SmallWorldImpl_stub, and then call the remote method.
Therefore, we must implement its own stub, let it implement all remote methods of the remote object, first pass the Client end through a series of interceptors in the client side; before the Server end enters the actual modified method before passing Series Interceptors, all Interceptor speak "can" to enter the called method.
Dynamic Proxy in JDK1.3 makes this idea to be simply implemented.
Dynamic Proxy works Dynamic Proxy is implemented by two Class: java.lang.reflect.Proxy and java.lang.reflect.invocationHandler, the latter is an interface.
The so-called Dynamic Proxy is such a Class: It is a class generated at runtime, when generating it, you must provide a set of Interface to it, then the Class claims that it implements these interface. You can of course use the Class's instance as any of these interfaces. Of course, this Dynamic Proxy is actually a proxy. It doesn't work substantive jobs for you. When you generate it, you have to provide a handler, which takes over the actual work. The important ways in java.lang.Reflect.Proxy are as follows:
Public Static Class GetProxyclass (ClassLoader Loader, Class [] interfaces);
This method returns a Dynamic Proxy Class to you, but you have to provide a ClassLoader and a group of Interface definitions to it. Remember: Every Class has a corresponding ClassLoader in JVM, this proxy is its ClassLoader, which also said that it implements these interface.
Protected Proxy (InvocationHandler Handler);
Back to your Dynamic Proxy has such a constructor, and you can only use it to generate an instance. You are transferred to this handler for all the methods of all methods in the interface (based on this instance).
The simpler method is to generate an instance of Dynamic Proxy:
Public Static Object NewProxyInstance (ClassLoader Loader,
Class [] Interfaces,
InvocationHandler H)
Throws IllegalargumentException;
This method includes all parameters of the above two methods, and the meaning is the same.
Java.lang.Reflect.InvocationHandler has only one way:
Public Object Invoke (Object Proxy, Method Method, Object [] ARGS;
This method will be transferred to the call to the method in the Dynamic Proxy instance. Where Proxy is the Dynamic Proxy instance, while Method, ARGS is called methods and parameters.
Compare this method in java.lang.Reflect.Method:
Public Object Invoke (Object Obj, Object [] ARGS);
It is said that you now call the method.getname () method in the OBJ instance, the passing parameters are Args.
In fact, all InvocationHandler will call this method in Method, which is the only way to reach the true method.
To see an example:
Public Class Interceptor IMPLEments InvocationHander
{
Private Object Obj;
Public InvocationHandler (Object O) {OBJ = O; // Save an instance, use after}
Public Object Invoke (Object Proxy, Method Method, Object [] args) {System.out.Println ("Enter Interceptor"); method.invoke (obj, args); // Call true method}}
/ / Assuming OBJ is an instance of SmallWorldImpl SmallWorld SW = proxy.newinstance (). GetClassLoader (), obj.getClass (). GetDeclaredClasses (), new interceptor (4)); sw.hello (4);
For Hello (), the call is first passed through Interceptor's invoke () method.
Java.lang.Reflect package is so important that any slightly scale app is inseparable from it. It brings great flexibility to Java, but who tested its impact on system performance?
Ok, we should see how Rickard Oberg implements his Smart Stub.
DynamicClassLoader We already know that Stub Class is loaded into the JVM through the ClassLoad of the remote object (SmallWorldImpl), and RMI is a Class name of the "_stub" string after the remote object name. Here DynamicClassLoader is used to load the Class and Stub of the remote object:
//: DynamicClassLoader
Import java.net.urlclassloader;
Import java.net.URL;
Public Class DynamicClassLoader Extends UrlclassLoader {// static ----------------------------------------- --------------- Static Threadlocal CurrentClass = New Threadlocal ();
Public static class getcurrentclass () {return (class) currentclass.get ();
// Constructors --------------------------------------------------------------- --- Public DynamicClassLoader (URL [] URLS) {Super (URLS);
// public importation ---------------------------------------- Protected class Findclass (String Name) THROWS CLASSNOTFOUNDEXCEPTION {IF (Name.endSwith ("_ stub")) {name = name.substring (0, name.length () - 5);
// Get IMPL CLASS CL = loadingClass (Name);
// Assume That It Only Implements One Remote Interface CurrentClass.set (CL.GetInterface () [0]);
Return DynamicRemotestub.class;} else {return super.findclass (name);}}}
This Class has two points worth noting:
DynamicClassLoader inherits UrlclassLoader, and most user customization ClassLoad is inherits this ClassLoader. DynamicClassLoader requires only an overloaded Findclass (). If you want to load the Class name to end with "_stub", return to DynamicRemotestub's class; otherwise let UrlClassLoader load this Class. If you want to load the Class is stub class, findClass () To find all the Remote Interface implemented by the corresponding remote object, remember them, because later DynamicRemotestub wants to proxy these interface. For our example, Stub Class is SmallWorldImpl_stub, and the remote object is SmallWorldImpl, and the Remote Interface has only one: SmallWorld. To simplify the problem, the code assumes that the remote object only implements an interface and it is Remote Interface. Do some inspections when implementing, and some skills.
Remember these Remote Interface is a problem. Because the instance of DynamicRemotestub is still generated, we can't pass it into it. This uses ThreadLocal Class here. Threadlocal is a variable associated with Thread that belongs to a specific Thread. Because DynamicRemotestub's instance initialization method still uses this Thread, it can read these interface.
DynamicRemotestub and DynamicsTubhandlerrickard Dynamicremotestub are as follows:
Import java.lang.reflect.Proxy;
Import java.io. *;
Import java.rmi.server. *;
Public Class DynamicRemotestub Extends Remotestub {// Constructors ----------------------------------------- --------- Class Cl;
Public DynamicRemotestub (RemoteRef Ref) {Super (Ref); Cl = DynamicClassLoader.getCurrentClass ();
// Serializable Implementation ----------------------------------- Object Readresolve () throws objectStreamException {if (CL = = NULL) RETURN THIS;
DynamicStubHandler stubHandler = new DynamicStubHandler (); Object proxy = Proxy.newProxyInstance (cl.getClassLoader (), new Class [] {cl}, stubHandler); stubHandler.setProxy (this); cl = null; return proxy;}}
The critical code here is the readResolve () method. Java.io.Serializable API documentation said: Classes That Need to Designate a replacement When An Instance of It Is Read from The Stream Should Implement this Special Method with The Exact Signature.
Any-Access-Modifier Object Readresolve () Throws ObjectStreamException;
That is to say, if you implement Java.io.Serializable Interface and implement this ReadResolve method, then this object is sequencefully, when it is restored, the system is to perform this method, the result is serialized. Objects become another object!
The corresponding method is WriteReplace (), which is replaced with a new object when serialization write stream (stream).
So, here DynamicRemotestub is serialized and transmitted to the Client side to a new object, which is a Dynamic Proxy. This proxy implements all Remote methods of RemoteObject, and its handler is a DynamicsTubhandler instance.
So all remote calls in the Client end will be passed from this proxy to the DynamicsTubhandler.
The main part of DynamicsTubHandler is the invoke () method:
Public Class DynamicsTubHandler
Implements invocationhandler, java.io.serializable
{
Remotestub Stub;
// public ----------------------------------------------- --------- Public void setProxy (transotestub stub) {this.stub = stub;}
// InvocationHandler Implementation ------------------------------ Public Object Invoke (Object Proxy, Method Method, Object [] args) THROWS Throwable {System.Out.println ("DynamicsTubHandler!"); Return stub.getref (). Invoke (stub, method, args, gethash (method));}}
We already know that the remote call of the Client side eventually wounds in RemoteRef, and Remoteref is a member of DynamicRemotestub, so DynamicRemotestub will call its setProxy () method after generating this DynamicsTubHandler instance, so that the latter can find this Remoteref. .
This invoke () method mainly calls the transoteref's invoke () method, the method of calculating the Hash value is cumbersome, but it is not too much related to this article.
We can finally insert a variety of Interceptors at the Client side. Here is only a sentence to verify this.
So what about the SERVER? If we don't make a modification, it will be transferred directly to the method of SmallWorldImpl. So we have to join Interceptor on the Server end. The simple implementation of the new Server's new Server is as follows:
Import java.lang.reflect. *;
Import java.io. *;
Import java.net. *;
Import java.rmi. *;
Import java.rmi.server. *;
Import Java.rmi.Registry. *;
Public class simpleserver {public static void main (string [] args) {Try {// Create Remote Object UrlclassLoader Cl = New DynamicClassLoader (new url [] {new file ("demo"). tour ()});
Remote server = (transote) Cl.loadClass ("smallworldimpl"). Newinstance ();
// IT Should Be DynamicRemotestub System.Out.println ("Stubobject IS:" RemoteObject.Tostub (RemoteObject) Server .getClass ());
// Add log proxy to the remote object server = (transote) proxy.newproxyinstance (server.getclass (). getclassloader (), new class [] {smallworld.class}, new logproxy (server);
// add performance proxy to the remote object server = (transote) proxy.newproxyinstance (server.getclass (). getClassLoader (), new class [] {smallworld.class}, new performanceProxy (server);
UnicastRemoteObject.exportObject (Server); registry reg = locateregistry.createRegistry (1099); reg.bind (smallworld.name, server);} catch (exception e) {E.PrintStackTrace ();}}}
Server first creates an instance of DynamicClassLoader, then use it to load the SmallWorldImpl Class and create its instance, although this time a SmallWorldimpl is created by RMI, we will not use it.
Then Server creates a Dynamic Proxy for SmallWorldImpl, and its handler is a LogProxy instance for imitation log interceptor. Thereafter, the Dynamic Proxy of Dynamic Proxy is then created, and its handler is PerformanceProxy instance. The code of LogProxy is as follows:
Import java.lang.reflect. *;
Public Class LogProxy IMPLEments InvocationHandler
{
Object obj;
Public logProxy (Object O) {OBJ = O;
// InvocationHandler Implementation ------------------------------ Public Object Invoke (Object Proxy, Method Method, Object [] args) THROWS Throwable {system.out.println ("Invoke LogProxy"); Return Method.Invoke (Obj, Args);}}
Note how it is transferred to the next Interceptor or RemoteObject.
In this way, call from the Client side to reach RemoteObject:
PerformanceProxy -> LogProxy -> SmallWorldImpl
Server finally published this Dynamic Proxy (with unicastRemoteObject.exportObject), then use registry.bind () bind to Naming Server, Client can find it and use it. The CLIENT end does not have to change.
Note that Server's code is fully indeed not related to SmallWorld. Its information can be read from the configuration file. Do you want to implement this case if the Application Server is?
In this way, we understand the application of Dynamic Proxy in RMI STUB. We can use this technology to join many interesting interceptors, or even loadbalance also use it.
In fact, this technology is the cornerstone of JBoss. JBoss is more standardized to this technology, which has created Interceptor Interface and interceptor per Enterprise Bean can be configured. Such system flexibility is large, it is difficult to imagine. And how many new things can you do now with Java's Reflect package?
Reference: 1. Rickard Oberg "Howto: Use Dynamic Proxies AS RMI Stubs HOWTO: Export Dynamic Proxies As RMI Objects" 2. "Dynamic Proxy Classes" 3. Christoph G. Jung "ClassLoaders Revisited: HotDeploy"