Dynamic Proxy Classes

本文翻译自 Java SE 6 Technotes -《Dynamic Proxy Classes》, 少部分内容取自 Java SE API 中文版.

Table Of Contents

Introduction
Dynamic Proxy API
Serialization
Examples

Introduction

一个动态代理类(dynamic proxy class)是实现了一组在运行期指定的接口的类, 通过已实现接口中的某个接口而对代理类某实例的方法调用将被编码并分派给另一个具有同一接口的对象上(a method invocation through one of the interfaces on an instance of the class will be encoded and dispatched to another object through a uniform interface). 因此, 一个动态代理类可用于为一组接口创建类型安全的代理对象而不需预先(例如使用编译期工具)生成这些代理类. 对一个动态代理类的某个实例进行方法调用会被分派给该实例调用处理器(invocation handler)中的某一方法, 且这些调用会以一个 java.lang.reflect.Method 对象(标识要调用的这些方法)和一个类型为 Object 的数组(包含方法参数信息)进行编码.

对于那些需要提供对(实现接口的)对象的调用进行安全的反射分派(type-safe reflective dispatch of invocations on objects)的应用程序或者库来说, 动态代理类是非常有用的. 例如, 一个应用程序可以使用一个动态代理类去创建一个实现多个任意(继承自java.util.EventListener)事件监听器接口的对象, 以一种统一风格去处理各种各样的不同类型事件, 例如将所有这样的事件记录到一个文件中.

Dynamic Proxy Class API

一个动态代理类(dynamic proxy class) (以下简单引用为代理类(proxy class)) 是一个实现了一组在运行期间所指定的接口列表的类.
一个代理接口(proxy interface) 是指由代理类实现了的一个接口.

一个代理实例(proxy instance) 是代理类的一个实例.

Creating a Proxy Class

代理类及其实例是通过使用类 java.lang.reflect.Proxy 的静态方法创建的.

给定一个类加载器和一个接口数组, Proxy.getProxyClass 方法将为这个代理类返回 java.lang.Class 对象. 代理类将定义于所指定的类加载器中, 实现所有提供的接口. 如果对于具有相同顺序的接口数组, 在该类加载器中已经定义有一个代理类, 那么这个已有的代理类将会返回; 否则, 将为这些接口动态生成一个代理类并且定义在该类加载器中.

对于可能传递给 Proxy.getProxyClass 的参数有一些限制 :

如果违反任何限制, Proxy.getProxyClass 会抛出一个 IllegalArgumentException. 如果接口数组参数或者它的任何元素是 null, 会抛出一个 NullPointerException.

需注意的是所指定的代理接口的顺序是很重要的: 如果两个使用了相同接口组合的代理类请求, 但是这两个接口组合中接口顺序不同, 将会导致两个完全不同的代理类. 代理类由它们的代理接口的顺序来区分, 以便提供对确定性方法调用的编码, 如当代理接口中的两个或多个接口共享具有同样名字和参数签名的方法的情形下; 这个论点在下面的章节 Methods Duplicated in Multiple Proxy Interfaces 有更详细的描述.

因此不需要在每次使用相同类加载器和接口数组去调用Proxy.getProxyClass时都生成一个新代理类, 动态代理类 API 的实现应该保持有已生成代理类的缓存, 以这些代理类所对应的加载器和接口列表为 key. 实现应该注意不要以一种阻碍类加载器和所有代理类在适当时候被垃圾收集的方式去引用类加载器, 接口和代理类.

Proxy Class Properties

一个代理类具有如下属性:

Creating a Proxy Instance

每个代理类都具有一个 public 构造方法, 带有一个参数, 该参数是接口 InvocationHandler 的一个实现.

每个代理类都有一个与之关联的 invocation handler 对象, 也就是传递给该代理类构造方法的那个对象. 可以不必使用 reflection API 去访问公共构造方法, 也可以通过调用 Proxy.newProxyInstance 方法创建一个代理实例, 该方法包含调用 Proxy.getProxyClass 的操作和对含 invocation handler 参数的构造方法的调用(which combines the actions of calling Proxy.getProxyClass with invoking the constructor with an invocation handler). 如果遇到 Proxy.getProxyClass 一样的问题, Proxy.newProxyInstance 也会抛出 IllegalArgumentException.

Proxy Instance Properties

一个代理实例有如下属性:

Methods Duplicated in Multiple Proxy Interfaces

当代理类的两个或多个接口包含一个具有相同名称和参数签名的方法时,代理类的接口顺序变得非常重要。在代理实例上调用这种重复方法 时,传递到调用处理程序( invocation handler )的 Method 对象没有必要成为其声明类可以从接口(通过该接口调用代理方法)的引用类型指派的对象。此限制存在的原因是,生成的代理类中的相应方法实现无法确定它通过哪一个接口调用。因此,在代理实例上调用重复方法时,第一个接口中的方法的 Method 对象包含接口的代理类列表中的方法(直接或通过超级接口继承),该对象会传递到调用处理程序的 invoke 方法,无论该方法调用通过哪一种引用类型发生。

如果代理接口包含某一方法,它的名称和参数签名与 java.lang.ObjecthashCodeequalstoString 方法相同,那么在代理实例上调用这样的方法时,传递到调用处理程序的 Method 对象将使 java.lang.Object 成为其声明类。换句话说,java.lang.Object 公共的非 final 方法理论上在所有代理接口之前,用于确定哪一个 Method 对象传递到调用处理程序。

还要注意,当重复方法被指派到调用处理程序时,invoke 方法可能只抛出 checked 异常类型,该异常类型是可以指派给(可指派这里可看作是”等价于”或”存在于”)所有通过该代理接口进行调用的代理接口方法 throws 声明语句的某一个异常类型。如果 invoke 方法抛出一个 checked 的异常,该异常没有指派给(可指派这里可看作是”等价于”或”存在于”)任一代理接口(可通过它调用)中的方法所声明的任何一个异常类型,那么该代理实例上的调用将抛出一个 unchecked 的 UndeclaredThrowableException。此限制表示并非所有的经调用传递给 invoke 方法的 Method 对象上的 getExceptionTypes 方法所返回的异常类型都可以由 invoke 方法成功抛出。

Serialization

由于 java.lang.reflect.Proxy 实现了 java.io.Serializable, 代理实例可以被序列化, 就如本节描述那样.  如果一个代理实例包含一个 invocation handler, 然而其又没有实现 java.io.Serializable, 那么如果将这样的实例写入一个java.io.ObjectOutputStream 中将抛出一个 java.io.NotSerializableException . 需注意的是对于代理类来说, 实现 java.io.Externalizable 与序列化时实现 java.io.Serializable 具有同样的效果:  Externalizable 接口的 writeExternalreadExternal 方法永远都不会在代理实例(或者 invocation handler)上被调用作为其序列化过程的一部分. 正如所有 Class 对象一样, 用于一个代理类的 Class 对象总是可序列化的.

一个代理类没有可序列化字段, 其 serialVersionUID 为 0L. 换句话说, 当一个代理类的 Class 对象被传递给 java.io.ObjectStreamClass 的静态 lookup 方法时, 返回的 ObjectStreamClass 实例将具有如下属性:

对象序列化的流协议支持一种类型代码叫做 TC_PROXYCLASSDESC, 这是流格式语法的一个结束信号(terminal symbol); 它的类型和值由 java.io.ObjectStreamConstants 接口的如下常量字段定义:

    final static byte TC_PROXYCLASSDESC = (byte)0x7D;

The grammar also includes the following two rules, the first being an alternate expansion of the original newClassDesc rule:

newClassDesc:
TC_PROXYCLASSDESC newHandle proxyClassDescInfo

proxyClassDescInfo:
(int)<count> proxyInterfaceName[count] classAnnotation superClassDesc

proxyInterfaceName:
(utf)

当一个 ObjectOutputStream 序列化一个代理类的类描述符时, 就如由传递该代理类的 Class 对象给 Proxy.isProxyClass 方法所确定的那样, 它使用 TC_PROXYCLASSDESC 类型代码而不是 TC_CLASSDESC, 遵照以上所述规则. 在 proxyClassDescInfo 的扩展中, proxyInterfaceName 项的先后次序是该代理类所实现的所有接口的名字, 按照调用 Class 对象的 getInterfaces 方法时所返回的顺序. classAnnotation and superClassDesc 具有它们在 classDescInfo 规则中一样的含义. 对于代理类, superClassDesc 是它的 super 类 - java.lang.reflect.Proxy - 的类描述符; including this descriptor allows for the evolution of the serialized representation of the class Proxy for proxy instances.

对于非代理类, ObjectOutputStream 调用它的 protected annotateClass 方法以允许子类将自己的数据写入一个特定类的流中. 对于代理类, 就不是调用 annotateClass, 而是调用 java.io.ObjectOutputStream 中的如下方法, 并传递该方法 Class 对象:

    protected void annotateProxyClass(Class cl) throws IOException;

在 ObjectOutputStream 里, annotateProxyClass 的默认实现没做任何事情.

当一个 ObjectInputStream 遇到类型码 TC_PROXYCLASSDESC, 它会从流中为代理类反序列化类描述符, 格式如上文所述. 不会为该类描述符调用它的 resolveClass 方法解析 Class 对象, 而是调用java.io.ObjectInputStream 的如下方法:

    protected Class resolveProxyClass(String[] interfaces)
	throws IOException, ClassNotFoundException;

在代理类描述符中被反序列化的这组接口名称会作为 interfaces 参数被传递给 resolveProxyClass.

ObjectInputStream 的 resolveProxyClass 的默认实现返回调用 Proxy.getProxyClass 并传递一组 Class 对象作为 interfaces 参数的结果. 用于每个名为 i 的接口的 Class 对象就是调用如下方法所返回的结果

	Class.forName(i, false, loader)

这里, loader 是位于执行栈顶部的第一个非空类加载器, 或者当栈中没有非空类加载器时为 null.  这是 resolveClass 方法默认行为中相同的类加载器选择. loader 的值也是传递给 Proxy.getProxyClass 的类加载器. 如果 Proxy.getProxyClass 抛出 IllegalArgumentException, resolveClass 也将抛出 ClassNotFoundException 包含有 IllegalArgumentException. 因为代理类永远不会有它自己的序列化字段, 在代理实例的流描述中, classdata[] 完全由用于它的 super 类 – java.lang.reflect.Proxy – 的实例数据组成. Proxy 有一个序列化字段, h, 包含用于该代理实例的 invocation handler.

Examples

这是一个简单的例子, 在(实现了随意的一组接口)对象上的方法调用之前或之后会打印一条消息:

public interface Foo {
    Object bar(Object obj) throws BazException;
}

-

public class FooImpl implements Foo {
    Object bar(Object obj) throws BazException {
        // ...
    }
}

-

public class DebugProxy implements java.lang.reflect.InvocationHandler {

    private Object obj;

    public static Object newInstance(Object obj) {
	return java.lang.reflect.Proxy.newProxyInstance(
	    obj.getClass().getClassLoader(),
	    obj.getClass().getInterfaces(),
	    new DebugProxy(obj));
    }

    private DebugProxy(Object obj) {
	this.obj = obj;
    }

    public Object invoke(Object proxy, Method m, Object[] args)
	throws Throwable
    {
        Object result;
	try {
	    System.out.println("before method " + m.getName());
	    result = m.invoke(obj, args);
        } catch (InvocationTargetException e) {
	    throw e.getTargetException();
        } catch (Exception e) {
	    throw new RuntimeException("unexpected invocation exception: " + e.getMessage());
	} finally {
	    System.out.println("after method " + m.getName());
	}
	return result;
    }
}

为 Foo 接口的实现创建一个 DebugProxy 并调用它的某个方法:

    Foo foo = (Foo) DebugProxy.newInstance(new FooImpl());
    foo.bar(null);

Here is an example of a utility invocation handler class that provides default proxy behavior for methods inherited from java.lang.Object and implements delegation of certain proxy method invocations to distinct objects depending on the interface of the invoked method:

import java.lang.reflect.*;

public class Delegator implements InvocationHandler {

    // preloaded Method objects for the methods in java.lang.Object
    private static Method hashCodeMethod;
    private static Method equalsMethod;
    private static Method toStringMethod;
    static {
	try {
	    hashCodeMethod = Object.class.getMethod("hashCode", null);
	    equalsMethod = Object.class.getMethod("equals", new Class[] { Object.class });
	    toStringMethod = Object.class.getMethod("toString", null);
        } catch (NoSuchMethodException e) {
	    throw new NoSuchMethodError(e.getMessage());
	}
    }

    private Class[] interfaces;
    private Object[] delegates;

    public Delegator(Class[] interfaces, Object[] delegates) {
	this.interfaces = (Class[]) interfaces.clone();
	this.delegates = (Object[]) delegates.clone();
    }

    public Object invoke(Object proxy, Method m, Object[] args)
	throws Throwable
    {
	Class declaringClass = m.getDeclaringClass();

	if (declaringClass == Object.class) {
	    if (m.equals(hashCodeMethod)) {
		return proxyHashCode(proxy);
	    } else if (m.equals(equalsMethod)) {
		return proxyEquals(proxy, args[0]);
	    } else if (m.equals(toStringMethod)) {
		return proxyToString(proxy);
	    } else {
		throw new InternalError("unexpected Object method dispatched: " + m);
	    }
	} else {
	    for (int i = 0; i < interfaces.length; i++) {
		if (declaringClass.isAssignableFrom(interfaces[i])) {
		    try {
			return m.invoke(delegates[i], args);
		    } catch (InvocationTargetException e) {
			throw e.getTargetException();
		    }
		}
	    }

	    return invokeNotDelegated(proxy, m, args);
	}
    }

    protected Object invokeNotDelegated(Object proxy, Method m, Object[] args)
	throws Throwable
    {
	throw new InternalError("unexpected method dispatched: " + m);
    }

    protected Integer proxyHashCode(Object proxy) {
	return new Integer(System.identityHashCode(proxy));
    }

    protected Boolean proxyEquals(Object proxy, Object other) {
	return (proxy == other ? Boolean.TRUE : Boolean.FALSE);
    }

    protected String proxyToString(Object proxy) {
	return proxy.getClass().getName() + '@' +
	    Integer.toHexString(proxy.hashCode());
    }
}

Subclasses of Delegator can override invokeNotDelegated to implement the behavior of proxy method invocations not to be directly delegated to other objects, and they can overrideproxyHashCodeproxyEquals, and proxyToString to override the default behavior of the methods the proxy inherits from java.lang.Object.

To construct a Delegator for an implementation of the Foo interface:

    Class[] proxyInterfaces = new Class[] { Foo.class };
    Foo foo = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
	proxyInterfaces,
	new Delegator(proxyInterfaces, new Object[] { new FooImpl() }));

Note that the implementation of the Delegator class given above is intended to be more illustrative than optimized; for example, instead of caching and comparing the Method objects for thehashCodeequals, and toString methods, it could just match them by their string names, because none of those method names are overloaded in java.lang.Object.

By javafuns on February 24, 2009 at 22:30 · Views: 519 · Permalink · RSS
Categorized in: Design Patterns, Java · Tagged with: ,
1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

Leave a Reply


  • Highest Rated

  • My PicasaPhotos

    IMG_0678.JPG

    1044829.jpg

    IMG_0657.JPG

  • RSS My del.icio.us

  • My RSS