介绍
一个动态代理类是实现了多个接口存在于运行时的类,这样,一个动态代理类所实现的接口的方法的调用会被编译并且通过一个统一的接口分配给另一个对象。因此,一个动态代理类可以在不预先生成代理类的情况下创建一个类型安全又实现了多个接口的代理对象,就像使用compile-time工具。动态代理类对象的方法调用被分配给InvocationHandler
的对象的一个单独的方法,然后通过java.lang.reflect.Method
的对象根据参数确定方法编译和执行。
应用或者是类库需要类型安全的反射传递、对象调用来实现接口API时,使用动态代理会很有帮助。例如,一个应用可以利用动态代理创建一个实现了多个任意事件监听接口的对象,而这些接口都是扩展了java.util.EventListener
接口,这样这个对象就可以用一种统一的风格来处理不同类型的事件,比如把所有这种事件记录到日志文件中。
动态代理类API
动态代理类(以下用代理类表示)是实现了多个接口并在运行时创建的类。
代理接口指被代理类实现的接口。
代理对象指代理类的实例对象。
创建代理类
代理类,代理对象用java.lang.reflect.Proxy
的静态方法创建。
Proxy.getProxyClass
方法用代理类的类加载器和一个接口数组返回一个java.lang.Class
对象。代理类将根据指定的类加载器定义并且实现所有指定的接口。如果类加载器已经存在了定义过的实现了相同排列接口的代理类,则返回该代理类。其它情况下,实现了这些接口的代理类会动态的生成并在指定的加载器中定义。
传递给Proxy.getProxyClass
的参数有如下一些限制:
- 接口数组中的所有
Class
对象必须表示的是接口,不是类或原始类型。 - 接口数组中不能有两个元素指向相同的
Class
对象。 - 所有的接口类型对于指定的类加载器必须是可视的,换句话说,对于类加载器
cl
和每一个接口i
,Class.forName(i.getName(),false,cl)==i
必须为true。 - 所有非共有的接口必须在相同的包下,否则代理类实现所有的接口则不可能,不管定义的是哪个包。
- 对任何指定的接口的成员方法集拥有相同的特征:
- 如果有一个方法返回的类型是原始类型或void,则所有的方法必须指定相同的类型。
- 另外,任何一个方法的返回类型必须是可以指定给其它方法的返回类型。
- 生成的代理类必须不超过虚拟机对类的任何限制。比如,虚拟机可以限制一个类的实现接口数为65535,则接口的数组大小必须不超过65535。
违法上面的任何一项限制,Proxy.getProxyClass
将抛出IllegalArgumentException。如
果接口数组参数或者其中的元素有null
,则会抛出NullPointerException
。
需要注意指定的代理接口的顺序是有意义的: 两个用相同的接口组合但顺序不同的代理类的请求会生成两个不同的代理类。代理类因为代理接口的顺序不同而有区别是为了提供确定的方法调用编码,防止两个或更多的接口共用同一个有相同名字和参数标识的方法。详细的描述可以参阅多个代理接口的重复方法。
所以通过Proxy.getProxyClass
传递相同的类加载器和接口并不谁生成一个新的代理类,动态代理类的实现应该存在一个代理类的缓存中,关键字就是与其一致的类加载器和接口序列。代理类的实现除了应该注意类加载器、接口外,还要注意这种方式的的代理类在占用时同时会阻止类的加载器及其含有的所有类被垃圾回收。
代理类的性质
一个代理类含有如下的性质:
- 代理类是public,final且非abstract.
- 代理类的绝对名字没有详细指定,但为代理类的名字保留以字符串"$Proxy"开头。
- 代理类继承了
java.lang.reflect.Proxy
。 - 代理类在创建的过程中完整有序实现了指定的接口。
- 如果一个代理类实现了一个
non-public
的接口,则代理类会生成在该接口所在的包下。其它情况,代理类的包未被指定。注意在运行时闭包行为不会阻止一个代理类在特定包下的成功定义,已经在相同的有特定签名的包下和相同的类加载器中定义的类也不会。 - 因为代理类在创建时实现了所有指定的接口,所以代理类的
Class
对象可以调用getInterfaces
得到一个包含相同序列的接口数组(按代理类创建时指定的序列),调用getMethods
得到包含所有实现接口的所有方法对象的数组,调用getMethod
得到想要的代理接口中的方法对象。 -
Proxy.isProxyClass
执行时当传递的代理类是通过Proxy.getProxyClass
返回的一个类或通过Proxy.newProxyInstance
返回的指定代理类的对象则为true
,其它情况为false
。这个方法的可靠性对制定安全决策是重要的,所以它的实现不仅仅可以测试一个继承java.lang.reflect.Proxy
的类的问题。 - 代理类的
java.security.ProtectionDomain
和被引导类载入器装在的系统类的保护域相同,比如java.lang.Object
,因为代理类的代码是被信任的系统代码生成的。这个保护域会典型的被授予java.security.AllPermission
。
创建代理对象
每个代理类有一个public
,参数为一个实现了InvocationHandler
接口的对象的构造方法。
每个代理对象含有一个关联的通过构造方法传进来的InvocationHandler
对象。代理对象的创建除了用反射的API进入public
构造方法实现外,还可以调用Proxy.newProxyInstance
方法来实现,这个方法综合了Proxy.getProxyClass并调用了其含有InvocationHandler
参数的构造方法。Proxy.newProxyInstance
如果抛出IllegalArgumentException
,原因和Proxy.getProxyClass
的相同。
代理对象的性质
代理对象有如下的性质:
-
创建一个代理对象proxy,它的代理类实现的一个接口Foo,
proxy instanceof Foo
会返回true;(Foo) proxy
类型转换会成功(不会抛出ClassCastException
)。 -
静态方法Proxy.getInvocationHandler会返回代理对象在创建时关联的
InvocationHandler
对象。如果传给Proxy.getInvocationHandler的参数不是一个代理对象,则会抛出IllegalArgumentException
。 -
代理对象调用的接口方法会被编译并分配给
InvocationHandler
对象的invoke方法,详细描述:代理对象自身会作为invoke方法的第一个参数传值,类型是Object。
invoke方法的第二个参数传的是与代理对象调用的接口方法一致的
java.lang.reflect.Method
对象。声明的方法类是接口中已经定义过的产生的对象,方法类也可以是代理类通过继承父代理接口里的。invoke的第三个参数是传入的代理对象的方法调用的对象数组。原始类型应该用包装类的
Class
对象,例如java.lang.Integer
或java.lang.Boolean
。invoke
方法的实现可以灵活的修改数组的内容。invoke方法的返回代理对象调用的方法的返回值。如果接口中方法声明的返回类型是原始类型,则invoke返回的值必须是初始类型的包装类的对象;其它情况,则必须是已经声明过的返回类型。如果ivoke返回的null且接口方法返回的类型是原始类型,则代理对象调用该方法会抛出
NullPointerException
。如果invoke方法返回的类型超过声明的类型,则代理对象会抛出ClassCastException
。如果invoke方法抛出一个异常,则代理对象调用对应的方法时也会抛出异常。抛出的异常类型必须是接口方法已经声明的或是非检查异常类型
java.lang.RuntimeException
或java.lang.Error
。如果invoke方法抛出的异常是一个检查异常并且没有在接口方法声明,则代理对象调用方法时会抛出UndeclaredThrowableException
。UndeclaredThrowableException
会被invoke方法抛出的异常重构。 -
代理对象中调用
hashCode
,equals
或者是toString
等在java.lang.Object
中定义的方法会被编码并分配给InvocationHandler
的invoke
方法,和上面描述的接口方法的调用被编码和分配的方式一样。声明的方法类对象传给invoke时是java.lang.Object
。代理对象从java.lang.Object
继承的其它放不会被代理类重写,所以调用这些方法就像java.lang.Object
的对象调用。
多个代理接口的重复方法
当一个代理类实现的两个或更多的接口中包含相同名字和参数标签的方法时,代理类的实现的接口的顺序就特别的重要。当重复的方法被代理对象调用时,方法类对象传递给InvocationHandler
不一定是代理对象调用的接口的含有相同标签的方法。这个限制的存在是因为代理类的生成的相同的方法不能决定那个方法会被调用。因此,当代理对象调用重复的方法时,代理中包含该方法的最靠前的接口(不管是直接定义的还是从父接口中继承的)的方法对象会被传递给InvocationHandler
对象的invoke方法,忽略已经传进来的方法调用的参数类型。
如果代理对象包含一个方法与hashCode
, equals
或者toString
等java.lang.Object
中的方法拥有相同的名字和参数标志,那代理对象调用该方法时,传递给InvocationHandler
对象的invoke
方法是声明该方法的类中的java.lang.Object
方法。换句话说,java.lang.Object
的public
,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
接口的方法writeExternal
和readExternal
永远不会再代理对象的序列化过程中被调用。就像所有的Class
对象,代理类的Class
对象也是可以序列化的。
一个代理类没有序列化的变量和一个serialVersionUID
。即,代理类的Class
对象传递给java.io.objectStreamClass
的静态方法lookup
,返回的ObjectStreamClass
对象有下面的性质:
- 调用它的
getSerialVersionUID
将方法返回OL。 - 调用它的
getFields
方法会返回一个长度为0的数组。 - 调用它的getField``方法参数为任意的
String
将返回null
。
支持对象序列化的流协议支持一个名字为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
返回的顺序。classAnnotation
和superClassDesc
执行classDescInfo
规则时意义相同。对于代理类,superClassDesc
是父类的描述符。java.lang.reflect.Proxy
,包括序列化的代理类和它演变的代理对象。
对于不是代理类的类,ObjectOutputStream
调用它的protected
的annotateClass
方法允许它的子类为一个特定类的流写入自定义数据。对于代理类,则会用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
会抛出一个包含IllegalArgumentException
的ClassNotFoundException
异常。因为代理类没有自己的序列化属性,代理对象的流的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;
}
}
构造一个实现了接口Foo
的DebugProxy
,并调用它的方法
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
,proxyEquals
和proxyToString
,重写代理类从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类更多是一个说明并不完善;例如,为hashCode
,equals
和toString
替换缓存和比较的Method
对象,它只是能根据它们的名字匹配,因为java.lang.Object中没用重载任何这些方法。
网友评论