技术开发 频道

Java 理论与实践: 用动态代理进行修饰



动态代理机制

    动态代理机制的核心是 InvocationHandler 接口,如清单 1 所示。调用句柄的工作是代表动态代理实际执行所请求的方法调用。传递给调用句柄一个 Method 对象(从 java.lang.reflect 包),参数列表则传递给方法;在最简单的情况下,可能仅仅是调用反射性的方法 Method.invoke() 并返回结果。


清单 1. InvocationHandler 接口

            public interface InvocationHandler {
            Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
            }
            

每个代理都有一个与之关联的调用句柄,只要代理的方法被调用时就会调用该句柄。根据通用的设计原则:接口定义类型、类定义实现,代理对象可以实现一个或多个接口,但是不能实现类。因为代理类没有可以访问的名称,它们不能有构造函数,所以它们必须由工厂创建。清单 2 显示了动态代理的最简单的可能实现,它实现 Set 接口并把所有 Set 方法(以及所有 Object 方法)分派给封装的 Set 实例。


清单 2. 包装 Set 的简单的动态代理

            public class SetProxyFactory {
            public static Set getSetProxy(final Set s) {
            return (Set) Proxy.newProxyInstance
            (s.getClass().getClassLoader(),
            new Class[] { Set.class },
            new InvocationHandler() {
            public Object invoke(Object proxy, Method method,
            Object[] args) throws Throwable {
            return method.invoke(s, args);
            }
            });
            }
            }
            

    SetProxyFactory 类包含一个静态工厂方法 getSetProxy(),它返回一个实现了 Set 的动态代理。代理对象实际实现 Set —— 调用者无法区分(除非通过反射)返回的对象是动态代理。SetProxyFactory 返回的代理只做一件事,把方法分派给传递给工厂方法的 Set 实例。虽然反射代码通常比较难读,但是这里的内容很少,跟上控制流程并不难 —— 只要某个方法在 Set 代理上被调用,它就被分派给调用句柄,调用句柄只是反射地调用底层包装的对象上的目标方法。当然,绝对什么都不做的代理可能有点傻,是不是呢?

什么都不做的适配器

    对于像 SetProxyFactory 这样什么都不做的包装器来说,实际有个很好的应用 —— 可以用它安全地把对象引用的范围缩小到特定接口(或接口集)上,方式是,调用者不能提升引用的类型,使得可以更安全地把对象引用传递给不受信任的代码(例如插件或回调)。清单 3 包含一组类定义,实现了典型的回调场景。从中会看到动态代理可以更方便地替代通常用手工(或用 IDE 提供的代码生成向导)实现的 Adapter 模式。


清单 3. 典型的回调场景

            public interface ServiceCallback {
            public void doCallback();
            }
            public interface Service {
            public void serviceMethod(ServiceCallback callback);
            }
            public class ServiceConsumer implements ServiceCallback {
            private Service service;
            ...
            public void someMethod() {
            ...
            service.serviceMethod(this);
            }
            }
            

ServiceConsumer 类实现了 ServiceCallback(这通常是支持回调的一个方便途径)并把 this 引用传递给 serviceMethod() 作为回调引用。这种方法的问题是没有机制可以阻止 Service 实现把 ServiceCallback 提升为 ServiceConsumer,并调用 ServiceConsumer 不希望 Service 调用的方法。有时对这个风险并不关心 —— 但有时却关心。如果关心,那么可以把回调对象作为内部类,或者编写一个什么都不做的适配器类(请参阅清单 4 中的 ServiceCallbackAdapter)并用 ServiceCallbackAdapter 包装 ServiceConsumerServiceCallbackAdapter 防止 ServiceServiceCallback 提升为 ServiceConsumer


清单 4. 用于安全地把对象限制在一个接口上以便不被恶意代码不能的适配器类

            public class ServiceCallbackAdapter implements ServiceCallback {
            private final ServiceCallback cb;
            public ServiceCallbackAdapter(ServiceCallback cb) {
            this.cb = cb;
            }
            public void doCallback() {
            cb.doCallback();
            }
            }
            

编写 ServiceCallbackAdapter 这样的适配器类简单却乏味。必须为包装的接口中的每个方法编写重定向类。在 ServiceCallback 的示例中,只有一个需要实现的方法,但是某些接口,例如 Collections 或 JDBC 接口,则包含许多方法。现代的 IDE 提供了“Delegate Methods”向导,降低了编写适配器类的工作量,但是仍然必须为每个想要包装的接口编写一个适配器类,而且对于只包含生成的代码的类,也有一些让人不满意的地方。看起来应当有一种方式可以更紧凑地表示“什么也不做的限制适配器模式”。

通用适配器类

清单 2 中的 SetProxyFactory 类当然比用于 Set 的等价的适配器类更紧凑,但是它仍然只适用于一个接口:Set。但是通过使用泛型,可以容易地创建通用的代理工厂,由它为任何接口做同样的工作,如清单 5 所示。它几乎与 SetProxyFactory 相同,但是可以适用于任何接口。现在再也不用编写限制适配器类了!如果想创建代理对象安全地把对象限制在接口 T,只要调用 getProxy(T.class,object) 就可以了,不需要一堆适配器类的额外累赘。


清单 5. 通用的限制适配器工厂类

            public class GenericProxyFactory {
            public static<T> T getProxy(Class<T> intf,
            final T obj) {
            return (T)
            Proxy.newProxyInstance(obj.getClass().getClassLoader(),
            new Class[] { intf },
            new InvocationHandler() {
            public Object invoke(Object proxy, Method method,
            Object[] args) throws Throwable {
            return method.invoke(obj, args);
            }
            });
            }
            }
0
相关文章