动态代理与AOP(7)
InvocationHandler使用的注意事项
Proxy类II
动态代理和AOP的最后一节课介绍一下InvocationHandler的使用注意事项
1. InvocationHandler使用的注意事项
1). InvocationHandler抛出空指针异常
这部分考虑的出发点是:
由于InvocationHandler的invoke方法返回值是Object类型(所有类型的父类),$Proxy的各种方法有各种类型的返回值。所以JVM会在$Proxy的方法中自动将invoke方法返回的Object类型的值做强制类型转换,转换到需要的子类或者基本数据类型。
(1). 一个invoke方法返回值是null的InvocationHandler的实现类
[1]. 实现类代码:
class InvocationHandlerX implementsInvocationHandler{
public Object invoke(Objectproxy, Method method, Object[] args)
throws Throwable {
System.out.print("调用的动态代理类的方法是:"+method.getName()+"**");
return null;
}
}
[2]. 测试代码
ClassLoader loader =Collection.class.getClassLoader();
Class<?>[] interfaces =ArrayList.class.getInterfaces();
InvocationHandler h =new InvocationHandlerX();
Collection proxyInstance =(Collection)Proxy.newProxyInstance(loader,interfaces, h);
(2). 方法的返回值类型是引用类型---测试代理类的toString()方法
[1]. 添加的测试代码
System.out.println(proxyInstance);
[2]. 打印结果
[3]. 结果分析
给出的打印结果是调用的动态代理类的toString方法。所以,要在头脑中勾画出动态代理类$Proxy0的toString方法,如下:
{1}. $Proxy0的toString方法代码
public String toString() {
Object retVal ="";
try {
Method method =this.getClass().getMethod("toString", null);
retVal =h.invoke(this, method, null);
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return (String) retVal;
}
【注意】这里把Object retVal ="";而不是null的原因就是invoke方法返回也是null,避免混淆,所以初始化赋值成""。
{2}.执行System.out.println(proxyInstance);涉及到的其他的方法的源码。便于理解先将这些方法的源码列出来:proxyInstance的类型是Collection类型,所以调用的是println重载方法:
{2}1. PrintStream类的println源码之一
public void println(Objectx) {
String s = String.valueOf(x);
synchronized (this) {
print(s);
newLine();
}
}
这个println先调用String的静态方法把Object类型的数据转换成String类型的数据,源码如下:
{2}2. String类的valueOf源码之一
public static StringvalueOf(Object obj) {
return (obj == null) ? "null" :obj.toString();
}
{3}.分析一下外面的System.out.println(proxyInstance);执行过程
{3}1.proxyInstance首先是被Proxy.newProxyInstance()方法实例化为Collection类型的对象。
{3}2.调用System.out.println()方法的时候,在println()内部通过String的valueOf方法先把Object对象,也就是proxyInstance对象映射成String类型的对象。根据valueOf中的执行过程:proxyInstance并不是null,所以String的valueOf方法继续调用proxyInstance的toString方法。
{3}3.此时动态代理类$Proxy0的toString方法就被调用了。这个方法被动态代理类进行了重写,重写的内容已给出。由于这个时候重载返回的是被强转的null,即(String)null
{3}4.此时println方法继续运行print(s);这句调用的是参数是Sting的print方法:
public void print(String s){
if (s == null) {
s= "null";
}
write(x);
}
由于此时s== null判断为true,此时这个方法将字符串"null"写入到控制台设备缓冲区。之后又往缓冲区打入一个换行符newLine(),最后打印出字符串"null"+换行
{3}5.由于在String.valueOf这一步调用了代理类对象的toString方法,所以会先打印出"调用的动态代理类的方法是:toString**"这个前缀,然后再打印出"null"+换行
(3). 方法的返回值类型是基本数据类型---测试代理类的add()方法
[1]. 添加的测试代码
proxyInstance.add("123");
[2]. 打印结果
[3]. 结果分析
{1}.先分析给出的异常信息提示
java.lang.NullPoinerException at $Proxy0.add(Unknown Source)表示异常出现在$Proxy0类动态生成的add方法。Unknown Source表示这个源文件没有字节码,这正好体现的是这个类是动态代理类,在文件系统没有字节码存在。所以给出的提示信息是UnknownSource。
{2}. println中首先是调用代理类的add方法,所以应该写出$Proxy0的add方法。注意Collection的add方法返回的是boolean类型,属于基本数据类型。但是invoke方法返回的是Object类型。从Object类型转换到基本数据类型只能强转到基本数据类型对应的包装类,这里只能强转到Boolean类型。然后Boolean类型再进行自动拆箱转换到boolean类型。因此$Proxy的add方法如下:
{3}. $Proxy0的add方法代码
public boolean add() {
Object retVal =null;
try {
Method method =this.getClass().getMethod("add", null);
retVal =h.invoke(this, method, null);
} catch (Throwable e) {
e.printStackTrace();
}
return (Boolean) retVal;
}
{4}.分析:$Proxy0的add方法在reurn语句之前的h.invoke(this, method, null);返回的是null,因此在return (Boolean) retVal;的时候首先把null转成了Boolean类型。自动拆箱的原理是基本包装类对象.xxxValue()。这个时候基本包装类对象是null,所以在Boolean对象基本数据类型boolean的时候调用了booleanValue()方法的时候抛出了NullPointerException。
{5}.【总结对比】
{5}1.toString方法本身返回的是引用类型,invoke的方法返回的是Object类型数据。所以从Object向子类引用强转的时候,不涉及调用返回值对象的方法。
{5}2. add方法本身返回的是基本数据类型,invoke的方法返回的是Object类型数据。所以从Object向包装类引用强转的时候,不涉及调用返回值对象的方法。但是包装类引用一定要自动拆箱成基本数据类型才能符合add方法本身返回的基本数据类型的要求。这个时候一定要调用包装类型引用的xxxxValue()方法。如果invoke返回的是null,这个时候null调用xxxValue()必定引发空指针异常。
(4). InvocationHandler产生NullPointerException的总结
[1]. 代理类代理目标类的方法返回值是基本数据类型
这个时候如果InvocationHandler的invoke方法返回的是null,此时必定引发空指针异常。
[2]. 代理类代理目标类的方法返回值是引用数据类型
这个时候如果InvocationHandler的invoke方法返回的是null,不会发生空指针异常。
2). InvocationHandler抛出堆栈溢出异常
(1). 在invoke方法内部打印了invoke方法接收到的第一个参数Object proxy对象
[1]. 实现类代码:
class InvocationHandlerY implementsInvocationHandler{
private Collection target =new ArrayList();
public Object invoke(Objectproxy, Method method, Object[] args)
throws Throwable {
System.out.println("调用的动态代理类的方法是:"+method.getName()+"**");
System.out.println(proxy);
return method.invoke(target, args);
}
}
[2]. 测试代码
ClassLoader loader =Collection.class.getClassLoader();
Class<?>[] interfaces =ArrayList.class.getInterfaces();
InvocationHandler h =new InvocationHandlerX();
CollectionproxyInstance =(Collection)Proxy.newProxyInstance(loader, interfaces,h);
System.out.println(proxyInstance.size());
[3]. 打印结果
(2). 打印结果分析
[1]. 打印信息表明首先调用的是$Proxy0的size方法。后来就是一直递归调用$Proxy0的toString方法。最后导致栈内存溢出错误。
[2]. 分析原因
{1}. 在add中调用了System.out.println(proxy);
proxy是Object类型并且非null,所以println内部调用的valueOf会调用proxy的toString方法。
{2}. 写出动态代理类对象的toString方法
public String toString() {
Object retVal =null;
try {
Method method =this.getClass().getMethod("toString", null);
retVal =h.invoke(this, method, null);
} catch (Throwable e) {
e.printStackTrace();
}
return (Boolean) retVal;
}
【注意】
{2}1. 这个方法invoke内部又调用System.out.println(proxy);这个又一次调用了proxy的toString方法。形成了现象就是proxy的toString方法和invoke方法互相调用。再直接剖析,即proxy的toString内部又一次调用了toString,形成了递归调用。
{2}2. 但是又没有递归结束条件,因此每次在Stack(栈中)生成的局部变量因为toString方法没有执行完而无法释放,最后直到系统为JVM分配的栈内存被占满。因此抛出错误 (已经不是异常了)
3). Object派发给动态代理类的方法
(1). 测试代理类的getClass方法
[1]. InvocationHandler的实现类 ---- InvocationHandlerX
class InvocationHandlerX implementsInvocationHandler{
public Object invoke(Objectproxy, Method method, Object[] args)
throws Throwable {
System.out.print("调用的动态代理类的方法是:"+method.getName()+"**");
return null;
}
}
[2]. 测试代码
ClassLoader loader =Collection.class.getClassLoader();
Class<?>[] interfaces =ArrayList.class.getInterfaces();
InvocationHandler h =new InvocationHandlerX();
Collection proxyInstance =(Collection)Proxy.newProxyInstance(loader,interfaces, h);
System.out.println(proxyInstance.getClass());
[3]. 测试结果
【疑问】
{1}. 首先从打印结果可以看出,没有打印出字样"调用的动态代理类的方法是:"
{2}. proxyInstance类型是Collection类型的,那么按照代理的原理,应该是打印出目标类Collection的getClass,就是java.util.Collection类型。
按照代理类的理论而言,打印的结果应该是"调用的动态代理类的方法是:getClass**interface java.util.Collection"这个结果。
但是结果是class$Proxy0。说明自定义的invoke方法没有被调用。
(2). Object委托给InvocationHandler的invoke方法生成的方法
Object类仅仅将hashCode、equals和toString方法派发给invoke方法生成动态代理类的对应的hashCode、equals和toString方法。Object其余的方法并不交给InvocationHandler的invoke方法来为动态代理类合成对应的方法。
1. Proxy类II
Proxy类的常用方法II(全部static)
(1). 判定一个类是否是动态类 -----判定
[1]. 功能描述
判定一个类是否是动态代理类
【注意】
{1}. 当且仅当指定的Class对象是由getProxyClass()方法或者newProxyInstance()方法动态生成的时候,这个判定方法返回true,否则返回false。
{2}. 不仅仅是这个类继承了java.lang.reflect.Proxy这个类,通过这个方法判定之后就一定返回true。一定是动态生成并继承了java.lang.reflect.Proxy这个类的子类通过这个方法检测才返回true。
[2]. 方法原型
public static boolean isProxyClass(Class<?> cl);
[3]. 方法输入参数
Class<?> c1:待检测是否是动态代理类的类对象引用
[4]. 方法的返回值类型:boolean
[5]. 测试代码
{1}. 自定义的继承java.lang.reflect.Proxy类的子类代码:
public class SubProxy extends Proxy {
protectedSubProxy(InvocationHandler h) {
super(h);
}
}
{2}. 测试代码1
Class subProxyClass =SubProxy.class;
System.out.println(Proxy.isProxyClass(subProxyClass));
{3}. 打印结果1:false
【分析】SubProxy是静态生成的字节码文件,而不是JVM运行的时候通过Proxy.getProxyClass()或者Proxy.newProxyInstance()动态生成的字节码,因此返回false。
{4}. 测试代码2
Class dynamicProxyClass =Proxy.getProxyClass(Thread.currentThread().getContextClassLoader(),Collection.class);
System.out.println(Proxy.isProxyClass(dynamicProxyClass));
{5}. 打印结果2:true
(2). 获取指定代理类的方法调用句柄实例-----获取
[1]. 功能描述
获取指定代理类对象相关联的InvocationHandler句柄实例。
[2]. 方法原型
public staticInvocationHandler getInvocationHandler(Object proxy) throwsIllegalArgumentException;
[3]. 方法输入参数
指定要获取方法调用句柄实例关联的代理类对象
[4]. 方法的返回值类型:InvocationHandler类型
[5]. 举例
{1}. InvocationHandler实现子类
class InvocationHandlerX implementsInvocationHandler{
private Collection target =new ArrayList();
public Object invoke(Objectproxy, Method method, Object[] args)
throws Throwable {
System.out.print("调用的动态代理类的方法是:"+method.getName()+"**");
return method.invoke(target, args);
}
}
{2}. 测试代码
ClassLoader loader =Collection.class.getClassLoader();
Class<?>[] interfaces =ArrayList.class.getInterfaces();
InvocationHandler h =new InvocationHandlerX();
Collection proxyInstance =(Collection)Proxy.newProxyInstance(loader,interfaces, h);
InvocationHandler invocationHandler =Proxy.getInvocationHandler(proxyInstance);
System.out.println(invocationHandler);
{3}. 打印结果