动态代理类(翻译)

2016-08-13  本文已影响0人  半黑月缺

原文: Dyanmic Proxy Classes

介绍

一个动态代理类是实现了多个接口存在于运行时的类,这样,一个动态代理类所实现的接口的方法的调用会被编译并且通过一个统一的接口分配给另一个对象。因此,一个动态代理类可以在不预先生成代理类的情况下创建一个类型安全又实现了多个接口的代理对象,就像使用compile-time工具。动态代理类对象的方法调用被分配给InvocationHandler的对象的一个单独的方法,然后通过java.lang.reflect.Method的对象根据参数确定方法编译和执行。

应用或者是类库需要类型安全的反射传递、对象调用来实现接口API时,使用动态代理会很有帮助。例如,一个应用可以利用动态代理创建一个实现了多个任意事件监听接口的对象,而这些接口都是扩展了java.util.EventListener接口,这样这个对象就可以用一种统一的风格来处理不同类型的事件,比如把所有这种事件记录到日志文件中。

动态代理类API

动态代理类(以下用代理类表示)是实现了多个接口并在运行时创建的类。

代理接口指被代理类实现的接口。

代理对象指代理类的实例对象。

创建代理类

代理类,代理对象用java.lang.reflect.Proxy的静态方法创建。

Proxy.getProxyClass方法用代理类的类加载器和一个接口数组返回一个java.lang.Class对象。代理类将根据指定的类加载器定义并且实现所有指定的接口。如果类加载器已经存在了定义过的实现了相同排列接口的代理类,则返回该代理类。其它情况下,实现了这些接口的代理类会动态的生成并在指定的加载器中定义。

传递给Proxy.getProxyClass的参数有如下一些限制:

违法上面的任何一项限制,Proxy.getProxyClass将抛出IllegalArgumentException。如果接口数组参数或者其中的元素有null,则会抛出NullPointerException

需要注意指定的代理接口的顺序是有意义的: 两个用相同的接口组合但顺序不同的代理类的请求会生成两个不同的代理类。代理类因为代理接口的顺序不同而有区别是为了提供确定的方法调用编码,防止两个或更多的接口共用同一个有相同名字和参数标识的方法。详细的描述可以参阅多个代理接口的重复方法

所以通过Proxy.getProxyClass传递相同的类加载器和接口并不谁生成一个新的代理类,动态代理类的实现应该存在一个代理类的缓存中,关键字就是与其一致的类加载器和接口序列。代理类的实现除了应该注意类加载器、接口外,还要注意这种方式的的代理类在占用时同时会阻止类的加载器及其含有的所有类被垃圾回收。

代理类的性质

一个代理类含有如下的性质:

创建代理对象

每个代理类有一个public,参数为一个实现了InvocationHandler接口的对象的构造方法。

每个代理对象含有一个关联的通过构造方法传进来的InvocationHandler对象。代理对象的创建除了用反射的API进入public构造方法实现外,还可以调用Proxy.newProxyInstance方法来实现,这个方法综合了Proxy.getProxyClass并调用了其含有InvocationHandler参数的构造方法。Proxy.newProxyInstance如果抛出IllegalArgumentException,原因和Proxy.getProxyClass的相同。

代理对象的性质

代理对象有如下的性质:

多个代理接口的重复方法

当一个代理类实现的两个或更多的接口中包含相同名字和参数标签的方法时,代理类的实现的接口的顺序就特别的重要。当重复的方法被代理对象调用时,方法类对象传递给InvocationHandler不一定是代理对象调用的接口的含有相同标签的方法。这个限制的存在是因为代理类的生成的相同的方法不能决定那个方法会被调用。因此,当代理对象调用重复的方法时,代理中包含该方法的最靠前的接口(不管是直接定义的还是从父接口中继承的)的方法对象会被传递给InvocationHandler对象的invoke方法,忽略已经传进来的方法调用的参数类型。

如果代理对象包含一个方法与hashCode, equals或者toStringjava.lang.Object中的方法拥有相同的名字和参数标志,那代理对象调用该方法时,传递给InvocationHandler对象的invoke方法是声明该方法的类中的java.lang.Object方法。换句话说,java.lang.Objectpublic,non-final方法逻辑上高于传给InvocationHandler的代理接口的方法。

注意当一个重复的方法传递给InvocationHandler时,invoke方法可能只会抛出可以调用的代理接口的方法抛出的异常。如果invoke方法抛出一个没有在可以调用的代理的接口的方法中指定的检查异常,则代理对象调用时会抛出UndeclaredThrowableException。这个限制表明通过getExceptionTypes返回的所有的传递给invoke方法的方法对象的异常并不都会被成功的抛出。

序列化

由于java.lang.reflect.Proxy实现了java.io.Serializable接口,所以代理对象是可以序列化的。然而,如果代理对象包含的InvocationHandler没有实现java.io.Serializable,则代理对象被写入java.io.ObjectOutputStream是会抛出java.io.NotSerializableException。对于代理类,实现java.io.Externalizable与实现java.io.Serializable的效果相同:Externalizable接口的方法writeExternalreadExternal永远不会再代理对象的序列化过程中被调用。就像所有的Class对象,代理类的Class对象也是可以序列化的。

一个代理类没有序列化的变量和一个serialVersionUID。即,代理类的Class对象传递给java.io.objectStreamClass的静态方法lookup,返回的ObjectStreamClass对象有下面的性质:

支持对象序列化的流协议支持一个名字为TC_PROXYCLASSDESC的类型代码,它是流格式语法的一个终止符号。java.io.ObjectStreamConstants接口中定义了它的类型和值:

`final static byte TC_PROXYCLASSDESC = (byte)0x7D;`

语法还包括连个规则,第一个是替代扩展原始newClassDesc规则:

newClassDesc:

TC_PROXYCLASSDESC newHandle proxyClassDescInfo

proxyClassDescInfo:

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

proxyInterfaceName: (utf)

ObjectOutputStream序列化一个类,可以用Proxy.isProxyClass判断Class对象是否是代理类。如果是代理类,则用TC_PROXYCLASSDESC类型码代替TC_CLASSDESC。proxyClassDescInfo的扩展中,proxyInterfaceName的队列是代理类实现的所有接口的名字,顺序是Class对象调用getInterfaces返回的顺序。classAnnotationsuperClassDesc执行classDescInfo规则时意义相同。对于代理类,superClassDesc是父类的描述符。java.lang.reflect.Proxy,包括序列化的代理类和它演变的代理对象。

对于不是代理类的类,ObjectOutputStream调用它的protectedannotateClass方法允许它的子类为一个特定类的流写入自定义数据。对于代理类,则会用java.io.ObjectOutputStream下面的方法代替annotateClass:

protected void annotateProxyClass(Class cl) throws IOException;

实现ObjectOutputStream的默认annotateProxyClass方法不做任何操作。

ObjectInputStream遇到类型符TC_PROXYCLASSDESC,表示反序列化流中的代理类。对于代理类,java.io.ObjectInputStream根据类的描述符区分,并用下面的方法替代resolveClass来解析Class对象:

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

要反序列化的代理类实现的接口名字的数组作为参数传递给resolveProxyClass方法。

ObjectInputStream的实现的默认resolveProxyClass方法返回值是调用Proxy.getProxyClass``的Class对象的interfaces参数的接口名字列表。每个用到的Class对象的接口名字i调用下面方法得到:

Class.forName(i, false, loader)

loader是执行栈中第一个非空的类加载器,如果执行栈中没有非空的类加载器则传null。这是resolveClass方法默认的选择类加载器的行为。和传递的Proxy.getProxyClass的类加载器的值相同。如果Proxy.getProxyClass抛出IllegalArgumentException,则resolveClass会抛出一个包含IllegalArgumentExceptionClassNotFoundException异常。因为代理类没有自己的序列化属性,代理对象的流的classdata[]包含了完整的对象数据,包含它的父类java.lang.reflect.Proxy。Proxy有一个序列化的属性,h,包含了代理对象的InvocationHandler。

示例

这是一个在一个实现了任意接口的对象的方法的执行前后打印一条信息的简单例子:

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;
    }
}

构造一个实现了接口FooDebugProxy,并调用它的方法

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

这是一个实用的InvocationHandler的例子,提供了继承于java.lang.Object的默认的代理方法行为,实现了特定代理的委托的方法的调用以区别与那些依靠接口调用方法的对象:

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());
}
}

Delegator的父类可以重写invokeNotDelegated实现调用的代理方法的行为而不用直接委托给其它对象。也可以重写proxyHashCode,proxyEqualsproxyToString,重写代理类从java.lang.Object继承的方法的默认行为。

构造一个实现了Foo接口的Delegator

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

注意实现的Delegator类更多是一个说明并不完善;例如,为hashCodeequalstoString替换缓存和比较的Method对象,它只是能根据它们的名字匹配,因为java.lang.Object中没用重载任何这些方法。

上一篇下一篇

猜你喜欢

热点阅读