`

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

阅读更多
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 Proxy 动态代理

    在Java中,动态代理主要通过`java.lang.reflect.Proxy`类和`java.lang.reflect.InvocationHandler`接口来实现。Proxy类是用于创建一个代理对象,而InvocationHandler接口则定义了代理对象调用方法时的行为。 1. **...

    Java动态代理ReflectProxyDemo

    - 框架:Spring的AOP、MyBatis的Mapper等大量使用动态代理。 7. **优缺点** 优点:灵活性高,可以在不修改原有代码的情况下扩展功能;减少代码重复,提高代码复用。 缺点:性能损耗,因为涉及到额外的反射和接口...

    java分页 动态代理 aop实现

    这些代码可能涉及到如何创建分页对象,如何与数据库交互获取分页数据,以及如何在AOP中使用动态代理进行拦截和处理。 总的来说,Java分页、动态代理和AOP是Java开发中的关键技术,它们能帮助我们构建出更加高效、可...

    java dtdl 动态代理

    它通过实现`java.lang.reflect.InvocationHandler`接口并使用`java.lang.reflect.Proxy`类来创建代理对象。`InvocationHandler`接口定义了一个方法`invoke()`,当通过代理对象调用任何方法时,实际上都会调用这个...

    java动态代理反射机制

    在实际应用中,动态代理和反射可以结合使用,例如,我们可以创建一个动态代理来包装`DatagramSocket`类的实例,这样在每次发送和接收数据时,都可以利用反射调用原始方法,并通过代理类添加额外的功能,如错误处理、...

    java中动态代理,springAop.pdf

    Java中的动态代理是一种强大的设计模式,它允许我们在运行时创建具有特定行为的代理对象,这些对象可以对原对象的方法调用进行拦截、增强或包装。Spring AOP(面向切面编程)是Spring框架的一部分,它利用了Java的...

    包含静态代理和动态代理demo代码

    在这个“包含静态代理和动态代理demo代码”的压缩包中,我们可能会看到两种常见的Java代理实现方式的示例:静态代理和动态代理。 首先,我们来详细讲解静态代理。在静态代理中,代理类和真实类(目标类)都是在编译...

    java代理实例可供参考

    在这个“java代理实例”中,我们将会深入探讨Java动态代理(Dynamic Proxy)以及静态代理(Static Proxy)的概念,这两种方法都是实现代理模式的关键。 一、Java动态代理 动态代理是Java内置的机制,主要依赖于`...

    java反射之动态代理

    总的来说,Java反射和动态代理提供了一种强大的工具,允许我们在运行时探索和操作类的结构,实现代码的动态扩展和灵活控制。这对于构建可扩展、可维护的系统至关重要,尤其是在需要实现复杂逻辑如日志记录、性能监控...

    代理模式及动态代理资料和源代码

    Spring AOP使用动态代理来实现切面功能。它可以基于JDK动态代理(如果目标类实现了接口)或CGLIB(如果目标类没有接口)。Spring AOP代理可以在方法调用前后自动插入切面逻辑,如事务管理、性能监控等。 5. **代理...

    ASM操作字节码,动态生成Java类class文件

    ASM可以被用来创建Java代理、实现元编程、甚至深入到Java虚拟机(JVM)层面进行性能优化。在Java开发中,ASM库允许我们直接操作字节码,这对于理解和实践AOP(面向切面编程)的概念尤其有用,就像Spring框架中的AOP...

    Java-Dynamic-Proxy:动态代理实现

    Java动态代理是Java编程中一个非常重要的特性,它允许我们在运行时创建具有特定接口的代理对象,这些代理对象可以自动地拦截并处理方法调用。动态代理机制在很多场景下都发挥着关键作用,比如AOP(面向切面编程)、...

    java 代理demo

    在"ProxySimple"这个例子中,可能包含了一个简单的代理实现,比如创建了一个代理类来包装一个实际的业务对象。代理类可能会在执行业务方法前后加入一些附加操作,例如记录日志、验证权限等。具体实现细节需要查看源...

    JAVA真实包装项目经验-二手车项目面试

    ### JAVA真实包装项目经验——二手车项目面试知识点梳理 #### 一、项目开发流程 1. **项目立项**:项目的启动阶段,确定项目的目标、范围、预算等。 2. **需求分析会议**: - 项目经理、研发部和产品部共同参与。 ...

    动态代理工程

    这个"动态代理工程"示例可能包含了一个或多个Java源文件,展示了如何使用Java的动态代理机制来增强或扩展已有对象的行为。 动态代理主要通过两个核心类实现:`java.lang.reflect.Proxy` 和 `java.lang.reflect....

    第6讲 动态代理是基于什么原理1

    1. **JDK动态代理**:基于Java的反射机制,通过实现`java.lang.reflect.InvocationHandler`接口并调用`java.lang.reflect.Proxy`类的`newProxyInstance()`方法来创建代理对象。`InvocationHandler`接口定义了一个`...

    疯狂JAVA讲义

    学生提问:构造器是创建Java对象的途径,是不是说构造器完全负责创建Java对象? 141 5.5.2 构造器的重载 142 学生提问:为什么要用this来调用另一个重载的构造器?我把另一个构造器里的代码复制、粘贴到这个构造器...

    PB调用JAVA WEBSERVICE实例

    2. **添加Java库到PB项目**:将生成的Java代理类JAR文件添加到PB项目的类路径中,以便PB可以识别并加载这些类。 3. **创建PB数据窗口**:在PB中创建一个数据窗口,用于包装Web服务的调用。数据窗口可以映射到Web...

    java的jdk6安装包

    2. 动态代理:JDK6引入了`java.lang.reflect.Proxy`类,允许动态创建代理对象,实现接口方法的调用。 3. 注解(Annotation):增强了代码的元数据功能,可以用于编译时检查、运行时处理等。 4. 自动装箱与拆箱:简化...

Global site tag (gtag.js) - Google Analytics