分享
 
 
 

Dynamic Proxy在Java RMI中的应用

王朝java/jsp·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

Dynamic Proxy 在 Java RMI 中的应用

Forest Hou

2000年9月6日,Rickard Oberg (Jboss 的主要作者之一) 在RMI-USERS 邮件组贴了一个标题为 "HOWTO: Use Dynamic Proxies as RMI stubs + HOWTO: Export dynammic proxies as RMI objects" 的贴子, 展示了Dynamic Proxy 如何用在Java RMI 中。利用这项技术,程序员可以在RMI中加入各种服务 (Authentication, Logging, Transaction 等等) 而不改变RMI的 interface。这一技术是JBoss实现EJB模型的关键。本文详细介绍了这一技术。Rickard曾用"SmartWorld"展示这一技术,并放在他的网站上供下载。遗憾的是我们今天已经无法看到SmallWorld了。笔者调通了Rickard帖子中的例子,称之为"SmallWorld",在本文中讲解。读者可以下载这个例子。

在RMI中加入Interceptor我非常喜欢Java RMI。相对于其他的分布式对象模型 (CORBA, COM) ,RMI 显得很轻,很简单。但是有时候程序员想在RMI中加入各种服务(service)而不改变其interface,却不是那么简单。"Interceptor"作为一种重要的"Design Pattern"在现代软件技术中非常流行,它通常用来实现service,因此是实现FrameWork的关键技术之一。

下面是一个非常简单的RMI例子。我要在这个例子上加入Interceptor,用以展现神奇的Dynamic Proxy技术。

//: 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 是一个简单的RMI interface,其中只定义了一个方法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是SmallWorld interface 的实现。这是一个再标准不过的RMI实现,你可以在任何RMI教科书中找到类似的例子。

Client的实现如下:

//: 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首先用RMI Naming Service找到SmallWorld的Stub对象,然后调用它的远程方法"hello()"。

最后是一个简单的Server,它实现一个SmallWorldImpl的实例,然后把它bind到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();

Registry reg = LocateRegistry.createRegistry(1099);

reg.bind(SmallWorld.NAME, server);

} catch (Exception e) {

e.printStackTrace();

}

}

}

大多数的RMI应用程序都是基于上面的模型实现的。可是这一模型很难加入security, log 等服务。因为一个正式的RMI应用系统中的远程对象和远程数量非常巨大,而且时常处于变动之中,给每个方法(Method)加上个数不定的服务(service)是不现实的。笔者曾参预的一个项目曾有很不错的设想:系统欲实现session管理,因此引入了session key。问题是每次远程调用都要传递session key,每一个method都要检查session key,相信没有程序员愿意这么做。结果当然可想而知啦。

实际上,每一个不错的FrameWork都会采用类似下面的实现方案:

这里Interceptors用来实现各种可插入的service,例如authentication, access controller, logging, performance metrics, transaction 等等。这种方案的关键在于各种service是可以配置的,它们和远程方法相对独立。因此写远程对象的程序员不必为代码中时不时要插入各种service的调用而烦恼,因为这些service已经在进入远程对象之前被执行了。

对于RMI,我们要求程序员的代码不改变或者极少改变,而service provider能加入各种interceptor。这可是一个很美妙的梦,Rickard Oberg经过不懈的探索终于实现了这一梦想。如今该技术已经成为Jboss的两大核心技术之一(另一核心技术是JMX) 。要理解该技术你必须稍微深入了解一下RMI的实现,Java Dynamic Proxy,ClassLoader,以及Object Serialization。这里我只是讲这些技术在RMI Stub中的应用,如果读者觉得困惑或者想吃透这些技术,请参阅本文所列参考资料。Rickard Oberg几乎每行代码都值得仔细推敲,笔者刚开始几乎什么都看不明白,遇到不懂就查阅相关资料,这才弄明白了其中的道理。:-)

深入Java RMI : RemoteObject, RemoteStub, RemoteRef, exportObject其实要想深入了解RMI是很困难的。只要看一看RMI的几个package包含多少class, interface, exception,你就明白了。相比之下,EJB包含的东西要少的多,而且interface之间的关系也非常简单明了。我现在感兴趣的是:程序员写的远程对象(SmallWorldImpl,不同于RemoteObject class,下面我"远程对象"表示这一级别的class,而用"RemoteObject"表示java RMI中的class) 实现时包含哪几部份?RMI是怎样创建它们的?

多数远程对象实现时都继承了UnicastRemoteObject,通过javac编译生成class文件后,再用rmic编译该class文件生成相应的Stub和Skeleton class。Stub class是该远程对象的远程代理,用于发出远程调用;Skeleton和远程对象处于同一地址,Skeleton用于接收,转发"调用"到该对象。

但是Skeleton并不是必需的。如果你在用rmic时加上"-v1.2" 的选项,生成的是Java2标准的Stub class ,这时没有Skeleton class。

上述关键部份用下面的UML图表示:

RemoteRef是RemoteObject的远程handle,它的作用体现在RemoteStub中。Client端的远程调用都是通过RemoteRef实行的。你可以用下面的命令生成SmallWorldImpl_Stub的源代码:

rmic -keep -v1.2 SmallWorldImpl其中hello()方法是这样实现的:

// implementation 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);

}

}而RemoteRef的prototype是:

public Object invoke(Remote obj, Method method, Object[] params, long opnum) throws Exception其中opnum是表示method的hash值,用来作authentication,这里我们不关心。我们将会看到正是该方法的反射性(reflection),使实现client端的interceptor成为可能。

下面我们来看一下Stub在server端的生成过程,以及client发现它的过程。

SmallWorldImpl的构造方法调用了super()方法,该方法实际调用到UnicastRemoteObject的构造方法,而UnicastRemoteObject的构造方法将调用exportObject()方法。exportObject试图用SmallWorldImpl的ClassLoader加载SmallWorldImpl_Stub class并生成它的实例,也就是:

// assume obj = SmallWorldImpl

String name = obj.getClass().getName();

Class c = Class.forName(name + “_Stub”, false, obj.getClass().getClassLoader());

RemoteStub stub = (RemoteStub)c.newInstance();然后把它发布到指定的socket上,这时候该远程对象就可以使用了。为了client端查找方便,server应该用Naming Service把这个Stub实例公布出来,这就是Registry.bind()起的作用。

java.rmi.registry.Registry

public void bind(String name, Remote object);该方法把SmallWorldImpl_Stub实例序列化(Serialization) ,再把序列化的object发送到Naming Server上去。

Server也可以直接调用UnicastRemoteObject的exportObject()方法公布远程对象。

Client端调用Registry.lookup()方法找到SmallWorldImpl_Stub的实例,然后就可以调用其中的远程方法了。

因此,我们必须实现自己的Stub,让它实现远程对象的所有远程方法,在调用RemoteRef的invoke()方法之前先通过client端的一系列interceptors;在server端进入实际的被调用方法之前也要通过一系列interceptors,所有的interceptor都说"可以"才进入被调用方法。

JDK1.3里出现的Dynamic Proxy使得这一设想能够简单地实现。

Dynamic Proxy 工作原理Dynamic Proxy是由两个class实现的:java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler,后者是一个interface。

所谓Dynamic Proxy是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然啦,这个Dynamic Proxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。:-)

java.lang.reflect.Proxy中的重要方法如下:

public static Class getProxyClass(ClassLoader loader, Class[] interfaces);该方法返回一个Dynamic Proxy Class给你,但你必须提供一个ClassLoader和一组interface定义给它。记住:每一个Class在JVM中都有一个对应的ClassLoader,这个Proxy就说这个loader是它的ClassLoader,它还说它实现了这些interface。

protected Proxy(InvocationHandler handler);返回给你的Dynamic Proxy有这样一个构造方法,而且你只能用它生成实例。你对那些interface中所有方法的调用(基于该实例) 都会传到这个handler中。

更简单的方法是一步生成Dynamic Proxy的实例:

public static Object newProxyInstance(ClassLoader loader,

Class[] interfaces,

InvocationHandler h)

throws IllegalArgumentException;这个方法包含了上面两个方法的所有参数,而且意义相同。

java.lang.reflect.InvocationHandler中只有一个方法:

public Object invoke(Object proxy, Method method, Object[] args);对于Dynamic Proxy实例中方法的调用都将调到这个方法。其中proxy正是那个Dynamic Proxy实例,而method,args是被调用方法和参数。

比较java.lang.reflect.Method中的这个方法:

public Object invoke(Object obj, Object[] args);它说的是你现在调用obj实例中的Method.getName()方法,传递的参数是args。

实际上,所有的InvocationHandler都会调用Method中的这一方法,它是到达真正方法的唯一途径。

来看一个实例:

public class Interceptor implements InvocationHander

{

private Object obj;

public InvocationHandler(Object o)

{

obj = o; //保存实例,以备后用

}

public Object invoke(Object proxy, Method method, Object[] args)

{

system.out.println(“Enter interceptor”);

method.invoke(obj, args); //调用真正的方法

}

}

//假设obj是一个SmallWorldImpl的实例

SmallWorld sw = Proxy.newInstance(obj.getClass().getClassLoader(),

obj.getClass().getDeclaredClasses(),

new Interceptor(obj));

sw.hello(4);

对于hello()的调用首先通过Interceptor的invoke()方法。

java.lang.reflect package是如此重要,以至于任何稍具规模的应用都离不开它。它给java带来巨大的灵活性,但是谁测试过它对系统性能的影响呢?

好了,我们该看看Rickard Oberg怎么实现他的smart stub了。

DynamicClassLoader我们已经知道Stub class是通过远程对象(SmallWorldImpl)的ClassLoader加载到JVM中的,而RMI是在远程对象名字之后加 "_Stub" 字符串形成Stub的class名字的。这里DynamicClassLoader用来加载远程对象的class和Stub的class:

//: 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 implementation -----------------------------------------

protected Class findClass(String name) throws ClassNotFoundException

{

if (name.endsWith("_Stub")) {

name = name.substring(0,name.length()-5);

// Get impl

Class cl = loadClass(name);

// Assume that it only implements one remote interface

currentClass.set(cl.getInterfaces()[0]);

return DynamicRemoteStub.class;

} else {

return super.findClass(name);

}

}

}

这个class有两点值得注意:

DynamicClassLoader继承了URLClassLoader,大多数用户定制(Customize) 的ClassLoader都继承这个ClassLoader。DynamicClassLoader只需重载(Overload) findClass()这一个方法。如果欲加载class名字以"_Stub" 结束,返回DynamicRemoteStub的class;否则让URLClassLoader加载这个class。

如果欲加载class 是Stub class,findClass()要找到对应的远程对象实现的所有remote interface,并且记住它们,因为以后的DynamicRemoteStub要代理这些interface。对于我们的例子,Stub class 是SmallWorldImpl_Stub,远程对象是SmallWorldImpl,Remote interface只有一个:SmallWorld。为了简化问题,代码假设远程对象只实现一个interface而且它就是Remote interface。实际实现时要做一些检查,并有些技巧。

记住这些Remote interface是一个问题。因为这时DynamicRemoteStub的实例还没有生成,我们没办法传到它里面。这里使用了ThreadLocal class。ThreadLocal是一个和Thread相关联的变量,它属于一个特定的Thread。因为DynamicRemoteStub的实例初始化方法仍用这个Thread,它能读出这些interface。

DynamicRemoteStub和DynamicStubHandlerRickard的DynamicRemoteStub如下:

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;

}

}

这里的关键代码在于readResolve()方法。java.io.Serializable API 文档说:

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;

就是说一个class如果实现了java.io.Serializable interface 并且实现了这个readResolve方法,那么这个对象在序列化(Serialization) 之后,被读出还原时,系统要执行这个方法,执行的结果是序列化的对象变成了另一个对象!

对应的方法是writeReplace(),它是在序列化写入流(stream) 时替换成新的对象。

所以,这里DynamicRemoteStub被RMI序列化并传到client端时变成了一个新的对象,它是一个Dynamic Proxy。这个Proxy实现了RemoteObject的所有Remote方法,而它的handler是一个DynamicStubHandler实例。

所以在client端的所有远程调用都会由这个Proxy传递到DynamicStubHandler中去。

DynamicStubHandler主要部分是那个invoke()方法:

public class DynamicStubHandler

implements InvocationHandler, java.io.Serializable

{

RemoteStub stub;

// Public --------------------------------------------------------

public void setProxy(RemoteStub 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));

}

}

我们已经知道client端的远程调用最终要绕到RemoteRef里,而RemoteRef是DynamicRemoteStub的成员,所以DynamicRemoteStub在生成这个DynamicStubHandler实例后要调用它的setProxy()方法把它自己传入,以便后者能找到这个RemoteRef。

这个invoke()方法主要是调用RemoteRef的invoke()方法,计算Hash值的方法很繁琐,但它和本文没太大关系。

我们终于可以在client端插入各种interceptor了。这里只打印了一句话验证这一点。

那么server端呢? 如果我们不作修改,它将直接调到SmallWorldImpl的方法里。所以我们还要在server端加入interceptor。

新Server的实现新Server的简单实现如下:

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").toURL() });

Remote server = (Remote)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 = (Remote)Proxy.newProxyInstance(server.getClass().getClassLoader(),

new Class[] { SmallWorld.class },

new LogProxy(server));

// add performance proxy to the remote object

server = (Remote)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首先创建一个DynamicClassLoader的实例,然后用它加载SmallWorldImpl class 并创建它的实例,虽然这时一个SmallWorldImpl的Stub对象会被RMI创立,我们却不会去用它。

然后Server创建了一个针对SmallWorldImpl的Dynamic Proxy,其handler是LogProxy实例,用以模仿Log interceptor。再其后创建针对Dynamic Proxy的Dynamic Proxy,其handler是PerformanceProxy实例。

LogProxy的代码如下:

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);

}

}

注意它是怎么向下一个interceptor或RemoteObject传递调用的。

这样,从client端来调用要经过如下途径到达RemoteObject:

PerformanceProxy --> LogProxy --> SmallWorldImplServer 最后把这个Dynamic Proxy 公布(用UnicastRemoteObject.exportObject) ,然后用Registry.bind() bind到Naming Server上,client 就可以找到它并用它了。而client端的代码根本不用改变。

注意Server的代码完全可以做到与SmallWorld无关。它的信息完全可以从配置文件中读出。想一下Application Server是不是这样实现的呢?

结束语这样我们就明白了Dynamic Proxy 在RMI Stub中的应用。我们可以用这一技术往系统中加入许多有趣的interceptor,甚至loadbalance也用它实现。

其实,该技术是JBoss的基石。JBoss对这一技术的应用更加规范化,专门创建了Interceptor interface,并且每个Enterprise Bean的Interceptor都是可以配置的。这样的系统灵活性之大,在以前难以想象。而今用java 的reflect package又能做出多少新东西呢?

参考资料:

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"

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有