选择显示字体大小

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

xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /> 


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


 


关键字 本文所属关键字

相关 与本文相关文章

分类 所有文章关键字导航

源码编程相关

Java   Asp   PHP   .Net   XML   C/C++   CGI   VB   Jsp   J2ee   J2se   J2me   EJB   Servlet   Tomcat   Resin   Struts   Weblogic   Eclipse   ANT   GUI   JMS   Web servise   IDEA   Webphere   Hibernate   Spring   Jboss   Applet   Swing   Socket   Javamail   Perl   Ajax   P2P   安全   模式   框架   测试   开源   游戏

SQL数据库相关

My-SQL   Ms-SQL   Access   DB2   Oracle   Sybase   SQLserver   索引   存储过程   加密   数据库   分页   视图  

手机无线相关

3G   Wap   CDMA   GRPS   GSM   IVR   彩信   短信   无线   增值业务

网页设计制作相关

HTML   CSS   网页配色   网页特效   Javascript   VBscript   Dreamweaver   Frontpage   JS   Web   网站设计

网站建设推广相关

建站经验   网站优化   网站排名   推广   Alexa

操作系统/服务器相关

Windows XP   Windows 2000   Windows 2003   Windows Me   Windows 9.x   Linux   UNIX   注册表   操作系统   服务器   应用服务器

图形图像多媒体相关

Photoshop   Fireworks   Flash   Coreldraw   Illustrator   Freehand   Photoimpact   多媒体   图形图像

标准 网站致力的规范

Valid CSS!

无不良内容,无不良广告,无恶意代码

Valid XHTML 1.0 Transitional

creativecommons