分享
 
 
 

用Java动态代理来创建包装器

王朝java/jsp·作者佚名  2008-05-31
窄屏简体版  字體: |||超大  

Java 1.3引入了名为“动态代理类”(Dynamic Proxy Class)的新特性,利用它可为“已知接口的实现”动态地创建包装器(wrapper)类。1.3版本问世以前,当我首次听说当时正在提议的动态代理类时,还以为它只是一种用来吸引人的眼球的特性。虽然把它包括到语言中是一件好事,但我却想不出它有任何实际用处。带着这一成见,我试着用动态代理写了一个示例程序,却惊奇于它的巨大威力,并当即决定把它放到我的工具箱中,以便在将来的项目中使用。此后,我不断体验到它的好处,它总是能用正确的方法来做你想要做的事情!

假如没有动态代理

深入探索动态代理类之前,先来看看在某些情况下,假如没有动态代理类会是什么样子:

public interface Robot {

void moveTo(int x, int y);

void workOn(Project p, Tool t);

}

public class MyRobot implements Robot {

public void moveTo(int x, int y) {

// stuff happens here

}

public void workOn(Project p, Tool t) {

// optionally destrUCtive stuff happens here

}

}

上述代码展示了一个名为Robot的接口,以及该接口的一个名为MyRobot的大致的实现。假定你现在想拦截对MyRobot类发出的方法调用(可能是为了限制一个参数的值)。

public class BuilderRobot implements Robot {

private Robot wrapped;

public BuilderRobot(Robot r) {

wrapped = r;

}

public void moveTo(int x, int y) {

wrapped.moveTo(x, y);

}

public void workOn(Project p, Tool t) {

if (t.isDestructive()) {

t = Tool.RATCHET;

}

wrapped.workOn(p, t);

}

}

一个办法就是使用显式的包装器类,就像上面显示的那样。BuilderRobot类在其构造函数中获取一个Robot,并拦截workOn方法,确保在任何项目中使用的工具都没有破坏性。另外,由于BuilderRobot这一包装器实现了Robot接口,所以凡是能够使用一个Robot的任何地方,都能使用一个BuilderRobot实例。

对于这种包装器风格的BuilderRobot来说,一旦你想修改或扩展Robot接口,它的缺点就会暴露无遗。为Robot接口添加一个方法,就得为BuilderRobot类添加一个包装器方法。为Robot添加10个方法,就得为BuilderRobot添加10个方法。假如BuilderRobot、CrusherRobot、SpeedyRobot和SlowRobot都是Robot包装器类,就必须分别为它们添加10个方法。这显然是效率极差的一种方案。

public class BuilderRobot extends MyRobot {

public void workOn(Project p, Tool t) {

if (t.isDestructive()) {

t = Tool.RATCHET;

}

super.workOn(p, t);

}

}

上述代码是对 BuilderRobot进行编程的另一种方式。注重BuilderRobot变成了MyRobot的一个子类。这样可解决在第2段代码的包装器方案中出现的问题。也就是说,修改Robot接口不必修改BuilderRobot。但这又产生了一个新问题:只有MyRobot对象才能是BuilderRobot。而在此之前,实现了Robot接口的任何对象都可以成为一个BuilderRobot。现在,由Java施加的“线性类出身限制”(linear class parentage restrictions)禁止我们将任意Robot(ArbitraryRobot)变成一个BuilderRobot。

动态代理也有限制

动态代理则综合了以上两种方案的优点。使用动态代理,你创建的包装器类不要求为所有方法都使用显式的包装器,创建的子类也不要求具有严格的出身,两者方法可任选一种你认为最好的。但是,动态代理仍然有一个限制。当你使用动态代理时,要包装/扩展的对象必须实现一个接口,该接口定义了预备在包装器中使用的所有方法。这一限制的宗旨是鼓励良好的设计,而不是为你带来更多的麻烦。根据经验,每个类都至少应该实现一个接口(nonconstant接口)。良好的接口用法不仅使动态代理成为可能,还有利于程序的模块化。

使用动态代理

下面的代码演示了用动态代理来创建一个BuilderRobot时所必需的类。注重我们创建的这个BuilderRobotInvocationHandler类甚至根本没有实现Robot接口。相反,它实现了java.lang.reflect.InvocationHandler,只提供了一个invoke方法。代理对象上的任何方法调用都要通过这一方法进行。观察invoke的主体,我们发现它会检查预备调用的方法的名称。假如这个名称是workOn,第二个参数就切换成一个非破坏性的工具。

然而,我们得到的仍然只是一个具有invoke方法的InvocationHandler,而不是我们真正想要的Robot对象。动态代理真正的魅力要到创建实际的Robot实例时才能反映出来。在源代码的任何地方,我们都没有定义一个Robot包装器或者子类。虽然如此,我们最终仍能获得一个动态创建的类,它通过调用BuilderRobotInvocationHandler的静态方法createBuilderRobot中的代码片断,从而实现了Robot接口,并集成了Builder工具过滤器。

import java.lang.reflect.Proxy;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

public class BuilderRobotInvocationHandler implements InvocationHandler {

private Robot wrapped;

public BuilderRobotInvocationHandler(Robot r) {

wrapped = r;

}

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

throws Throwable {

if ("workOn".equals(method.getName())) {

args[1] = Tool.RATCHET;

}

return method.invoke(wrapped, args);

}

public static Robot createBuilderRobot(Robot toWrap) {

return (Robot)(Proxy.newProxyInstance(Robot.class.getClassLoader(),

new Class[] {Robot.class},

new BuilderRobotInvocationHandler(toWrap)));

}

public static final void main(String[] args) {

Robot r = createBuilderRobot(new MyRobot());

r.workOn("scrap", Tool.CUTTING_TORCH);

}

}

createBuilderRobot中的代码表面上很复杂,但它的作用其实很简单,就是告诉Proxy类用一个指定的类加载器来动态创建一个对象,该对象要实现指定的接口(本例为Robot),并用提供的InvocationHandler来代替传统的方法主体。结果对象在一个instanceof Robot测试中返回true,并提供了在实现了Robot接口的任何类中都能找到的方法。

有趣的是,在BuilderRobotInvocationHandler类的invoke方法中,完全不存在对Robot接口的引用。InvocationHandlers并不是它们向其提供了“代理方法实现”的接口所专用的,你完全可以写一个InvocationHandler,并将其作为众多代理类的后端来使用。

但在本例中,我们以构造函数参数的形式,为BuilderRobotInvocationHandler提供了RobotInterface的另一个实例。代理Robot实例上的任何方法调用最终都由BuilderRobotInvocationHandler委托给这个“包装的”Robot。但是,虽然这是最常见的设计,但你必须了解,InvocationHandler不一定非要委托给被代理的接口的另一个实例。事实上,InvocationHandler完全能自行提供方法主体,而无需一个委托目标。

最后要注重,假如Robot接口中发生改变,那么BuilderRobotInvocationHandler中的invoke方法将反应迟钝。例如,假定workOn方法被重命名,那么非破坏性工具陷阱会静静地失败,这时的BuilderRobots就有可能造成损害。较轻易检测、但却不一定会造成问题的是workOn方法的重载版本。假如方法具有相同的名称,但使用一个不同的参数列表,就可能在运行时造成一个ClassCastException或者ArrayIndexOutOfBoundsException异常。为此,以下代码给出了一个解决方案,它能生成一个更灵活的BuilderRobotInvocationHandler。在这段代码中,任何时候在任何方法中使用一个工具,这个工具就会被替换成一个非破坏性工具。请试着用子类化处理或者传统的委托来进行试验。

import java.lang.reflect.Proxy;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

public class BuilderRobotInvocationHandler implements InvocationHandler {

private Robot wrapped;

public BuilderRobotInvocationHandler(Robot r) {

wrapped = r;

}

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

throws Throwable {

Class[] paramTypes = method.getParameterTypes();

for (int i=0; i < paramTypes.length; i++) {

if (Tool.class.isAssignableFrom(paramTypes[i])) {

args[i] = Tool.RATCHET;

}

}

return method.invoke(wrapped, args);

}

public static Robot createBuilderRobot(Robot toWrap) {

return (Robot)(Proxy.newProxyInstance(Robot.class.getClassLoader(),

new Class[] {Robot.class},

new BuilderRobotInvocationHandler(toWrap)));

}

public static final void main(String[] args) {

Robot r = createBuilderRobot(new MyRobot());

r.workOn("scrap", Tool.CUTTING_TORCH);

}

}

使用建议

在大多数开发环境中,用工具来取代Robot并不是一种常见的操作。还有其他许多方式可以使用动态代理。它们提供了一个调试层,可方便地记录一个对象上的所有方法调用的具体细节。它们可执行绑定检查,并对方法参数进行验证。在与远程数据源发生冲突的前提下,甚至可用它们将备用的本地测试后端动态地交换出去。假如你采用的是良好的、由接口驱动的设计方案,我个人觉得动态代理的用处肯定要比你想象的多,最终你会叹服于它从容解决许多问题的本事!

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
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- 王朝網路 版權所有