您的位置:首页 > 职场人生

黑马程序员--09.动态与代理AOP--07【InvocationHandler使用的注意事项】【Proxy类II】

2013-08-25 23:11 645 查看

动态代理与AOP----7

InvocationHandler使用的注意事项

Proxy类II

----------- android培训java培训、java学习型技术博客、期待与您交流! ------------
动态代理和AOP的最后一节课介绍一下InvocationHandler的使用注意事项

1.    InvocationHandler使用的注意事项

1). InvocationHandler抛出空指针异常

这部分考虑的出发点是:

由于InvocationHandler的invoke方法返回值是Object类型(所有类型的父类),$Proxy的各种方法有各种类型的返回值。所以JVM会在$Proxy的方法自动将invoke方法返回的Object类型的值强制类型转换转换到需要的子类或者基本数据类型

(1). 一个invoke方法返回值是null的InvocationHandler的实现类

[1]. 实现类代码:

class InvocationHandlerX implements InvocationHandler{
public Object invoke(Object proxy, Methodmethod, 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() {
ObjectretVal ="";
try {
Methodmethod =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(Object x) {
String s = String.valueOf(x);
synchronized (this) {
print(s);
newLine();
}
}


这个println先调用String的静态方法把Object类型的数据转换成String类型的数据,源码如下:
{2}2. String类的valueOf源码之一

public static String valueOf(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.NullPoinerExceptionat $Proxy0.add(Unknown Source)表示异常出现在$Proxy0类动态生成的add方法。Unknown Source表示这个源文件没有字节码,这正好体现的是这个类是动态代理类在文件系统没有字节码存在所以给出的提示信息是Unknown
Source。

{2}. println中首先调用代理类add方法,所以应该写出$Proxy0的add方法。注意Collection的add方法返回的是boolean类型,属于基本数据类型。但是invoke方法返回的是Object类型。从Object类型转换到基本数据类型只能强转到基本数据类型对应的包装类,这里只能强转到Boolean类型。然后Boolean类型再进行自动拆箱转换到boolean类型。因此$Proxy的add方法如下:

{3}. $Proxy0的add方法代码

public boolean add() {
ObjectretVal =null;
try {
Methodmethod =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 implements InvocationHandler{
private Collection target =new ArrayList();
public Object invoke(Object proxy, Methodmethod, 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();
Collection proxyInstance =(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() {
ObjectretVal =null;
try {
Methodmethod =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 implements InvocationHandler{
public Object invoke(Object proxy, Methodmethod, 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**interfacejava.util.Collection"这个结果。

但是结果是class $Proxy0。说明自定义的invoke方法没有被调用。

(2). Object委托给InvocationHandler的invoke方法生成的方法

Object类仅仅将hashCode、equals和toString方法派发给invoke方法生成动态代理类的对应的hashCode、equals和toString方法。Object其余的方法并不交给InvocationHandler的invoke方法来为动态代理类合成对应的方法。

2.    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 {
protected SubProxy(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
static
InvocationHandlergetInvocationHandler(Object proxy)
throws
IllegalArgumentException;
[3]. 方法输入参数

指定要获取方法调用句柄实例关联的代理类对象

[4]. 方法的返回值类型InvocationHandler类型

[5]. 举例

{1}.InvocationHandler实现子类

class InvocationHandlerX implements InvocationHandler{
private Collection target =new ArrayList();
public Object invoke(Object proxy, Methodmethod, 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}. 打印结果
InvocationHandlerX@1a1c887
----------- android培训java培训、java学习型技术博客、期待与您交流! ------------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐