技术开发 频道

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



动态代理作为 Decorator

    当然,动态代理工具能做的,远不仅仅是把对象类型限制在特定接口上。从 清单 2清单 5 中简单的限制适配器到 Decorator 模式,是一个小的飞跃,在 Decorator 模式中,代理用额外的功能(例如安全检测或日志记录)包装调用。清单 6 显示了一个日志 InvocationHandler,它在调用目标对象上的方法之外,还写入一条日志信息,显示被调用的方法、传递的参数,以及返回值。除了反射性的 invoke() 调用之外,这里的全部代码只是生成调试信息的一部分 —— 还不是太多。代理工厂方法的代码几乎与 GenericProxyFactory 相同,区别在于它使用的是 LoggingInvocationHandler 而不是匿名的调用句柄。


清单 6. 基于代理的 Decorator,为每个方法调用生成调试日志

            private static class LoggingInvocationHandler<T>
            implements InvocationHandler {
            final T underlying;
            public LoggingHandler(T underlying) {
            this.underlying = underlying;
            }
            public Object invoke(Object proxy, Method method,
            Object[] args) throws Throwable {
            StringBuffer sb = new StringBuffer();
            sb.append(method.getName()); sb.append("(");
            for (int i=0; args != null && i<args.length; i++) {
            if (i != 0)
            sb.append(", ");
            sb.append(args[i]);
            }
            sb.append(")");
            Object ret = method.invoke(underlying, args);
            if (ret != null) {
            sb.append(" -> "); sb.append(ret);
            }
            System.out.println(sb);
            return ret;
            }
            }
            

如果用日志代理包装 HashSet,并执行下面这个简单的测试程序:

    Set s = newLoggingProxy(Set.class, new HashSet());
            s.add("three");
            if (!s.contains("four"))
            s.add("four");
            System.out.println(s);
            

会得到以下输出:

  add(three) -> true
            contains(four) -> false
            add(four) -> true
            toString() -> [four, three]
            [four, three]
            

这种方式是给对象添加调试包装器的一种好的而且容易的方式。它当然比生成代理类并手工创建大量 println() 语句容易得多(也更通用)。我进一步改进了这一方法;不必无条件地生成调试输出,相反,代理可以查询动态配置存储(从配置文件初始化,可以由 JMX MBean 动态修改),确定是否需要生成调试语句,甚至可能在逐个类或逐个实例的基础上进行。

在这一点上,我认为读者中的 AOP 爱好者们几乎要跳出来说“这正是 AOP 擅长的啊!”是的,但是解决问题的方法不止一种 —— 仅仅因为某项技术能解决某个问题,并不意味着它就是最好的解决方案。在任何情况下,动态代理方式都有完全在“纯 Java”范围内工作的优势,不是每个公司都用(或应当用) AOP 的。

动态代理作为适配器

代理也可以用作真正的适配器,提供了对象的一个视图,导出与底层对象实现的接口不同的接口。调用句柄不需要把每个方法调用都分派给相同的底层对象;它可以检查名称,并把不同的方法分派给不同的对象。例如,假设有一组表示持久实体(PersonCompanyPurchaseOrder) 的 JavaBean 接口,指定了属性的 getter 和 setter,而且正在编写一个持久层,把数据库记录映射到实现这些接口的对象上。现在不用为每个接口编写或生成类,可以只用一个 JavaBean 风格的通用代理类,把属性保存在 Map 中。

清单 7 显示的动态代理检查被调用方法的名称,并通过查询或修改属性图直接实现 getter 和 setter 方法。现在,这一个代理类就能实现多个 JavaBean 风格接口的对象。


清单 7. 用于把 getter 和 setter 分派给 Map 的动态代理类

            public class JavaBeanProxyFactory {
            private static class JavaBeanProxy implements InvocationHandler {
            Map<String, Object> properties = new HashMap<String,
            Object>();
            public JavaBeanProxy(Map<String, Object> properties) {
            this.properties.putAll(properties);
            }
            public Object invoke(Object proxy, Method method,
            Object[] args)
            throws Throwable {
            String meth = method.getName();
            if (meth.startsWith("get")) {
            String prop = meth.substring(3);
            Object o = properties.get(prop);
            if (o != null && !method.getReturnType().isInstance(o))
            throw new ClassCastException(o.getClass().getName() +
            " is not a " + method.getReturnType().getName());
            return o;
            }
            else if (meth.startsWith("set")) {
            // Dispatch setters similarly
            }
            else if (meth.startsWith("is")) {
            // Alternate version of get for boolean properties
            }
            else {
            // Can dispatch non get/set/is methods as desired
            }
            }
            }
            public static<T> T getProxy(Class<T> intf,
            Map<String, Object> values) {
            return (T) Proxy.newProxyInstance
            (JavaBeanProxyFactory.class.getClassLoader(),
            new Class[] { intf }, new JavaBeanProxy(values));
            }
            }
            

    虽然因为反射在 Object 上工作会有潜在的类型安全性上的损失,但是,JavaBeanProxyFactory 中的 getter 处理会进行一些必要的额外的类型检测,就像我在这里用 isInstance() 对 getter 进行的检测一样。

0
相关文章