|
真爱的事业和真正的爱情一生只有一次,都值得我们温柔地相待,因为那种感觉是永远都无法复制的, 这世界真正属于你的东西其实并不多,你不好好珍惜,它便会离你而去,包括机遇,包括爱情,包括生命。 不要找任何理由, 当幸福在你身边的时候就抓住它,你就一定会很幸福! |
时 间 记 忆 |
« | September 2025 | » | 日 | 一 | 二 | 三 | 四 | 五 | 六 | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | | | | | |
|
blog名称:玻璃杯中的花生壳 日志总数:162 评论数量:249 留言数量:1 访问次数:828424 建立时间:2004年11月4日 |
 | | |
|
|
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并不是一种常见的操作。还有其他许多方式可以使用动态代理。它们提供了一个调试层,可方便地记录一个对象上的所
有方法调用的具体细节。它们可执行绑定检查,并对方法参数进行验证。在与远程数据源发生冲突的前提下,甚至可用它们将备用的本地测试后端动态地交换出去。
如果你采用的是良好的、由接口驱动的设计方案,我个人觉得动态代理的用处肯定要比你想象的多,最终你会叹服于它从容解决许多问题的本事! |
|
| | |
|