博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[转载]JDK的动态代理深入解析(Proxy,InvocationHandler)
阅读量:5050 次
发布时间:2019-06-12

本文共 5023 字,大约阅读时间需要 16 分钟。

创建Proxy对象,测试

public class ProxyTest_old {	publicstaticvoid main(String[] args) {		UserDao userDao = new UserDaoImpl();		LogHandler_old logHandler = new LogHandler_old(userDao);		UserDao userDaProxy = (UserDao) Proxy.newProxyInstance(userDao		.getClass().getClassLoader(), userDao.getClass()		.getInterfaces(), logHandler);		userDaProxy.delete(new User());		userDaProxy.save(new User());		userDaProxy.update(new User());	}}

解释:

1.Proxy即动态代理类;

2.Static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当作被代理类使用;

它有三个参数:

ClassLoader loader----指定被代理对象的类加载器

Class[] Interfaces----指定被代理对象所以事项的接口

InvocationHandler h----指定需要调用的InvocationHandler对象

3.实现InVocationHandler接口的LogHandler_old对象

这个对象的invoke()方法就是Proxy这个动态代理类所代理的接口类的抽象方法的真实实现;

它有三个参数:

Object proxy-----代理类对象

Method method-----被代理对象的方法(这里不是接口的抽象方法了,是具体的实现类中的方法)

Object[] args-----该方法的参数数组

JDK中具体的动态代理类是怎么产生的呢?

1.产生代理类$Proxy0类

执行了Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)

将产生$Proxy0类,它继承Proxy对象,并根据第二个参数,实现了被代理类的所有接口,自然就可以生成接口要实现的所有方法了(这时候会重写hashcode,toString和equals三个方法),但是还没有具体的实现体;

2.将代理类$Proxy0类加载到JVM中

这时候是根据Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)它的第一个参数----就是被代理类的类加载器,把当前的代理类加载到JVM中

3.创建代理类$Proxy0类的对象

调用的$Proxy0类的$Proxy0(InvocationHandler)构造函数,生成$Proxy0类的对象

参数就是Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)它的第三个参数

这个参数就是我们自己实现的InvocationHandler对象,我们知道InvocationHandler对象中组合加入了代理类代理的接口类的实现类;所以,$Proxy0对象调用所有要实现的接口的方法,都会调用InvocationHandler对象的invoke()方法实现;

4.生成代理类的class byte

动态代理生成的都是二进制class字节码

--------------------------------------------------------------------------------

动态代理是很多框架和技术的基础, spring 的AOP实现就是基于动态代理实现的。了解动态代理的机制对于理解AOP的底层实现是很有帮助的。

Proxy 类的设计用到代理模式的设计思想,Proxy类对象实现了代理目标的所有接口,并代替目标对象进行实际的操作。但这种替 代不是一种简单的替代,这样没有任何意义,代理的目的是在目标对象方法的基础上作增强,这种增强的本质通常就是对目标对象的方法进行拦截。所 以,Proxy应该包括一个方法拦截器,来指示当拦截到方法调用时作何种处理。InvocationHandler就是拦截器的接口。

InvocationHandler接口也是在java.lang.reflec

Object invoke(Object proxy, Method method, Object[] args)

这个接口有三个参数,其中第二和第三个参数都比较好理解,一个是被拦截的方法,一个是该方法的参数列表。关键是第一个参数。按照doc文档的解析,

proxy - the proxy instance that the method was invoked on

也就是说,proxy应该是一个代理实例,但为什么要传入这个参数呢?

带着这个问题,自己编了个小程序作了一点试验。

public interface IAnimal {	void info();}
public class Dog implements IAnimal{	public void info() {		System.out.println("I am a dog!");	}}
import java.lang.reflect.*;public class ProxyTest {	public static void main(String[] args) throws InterruptedException {		final IAnimal animal = new Dog();		Object proxyObj = Proxy.newProxyInstance(animal.getClass()				.getClassLoader(), animal.getClass().getInterfaces(),				new InvocationHandler() {					public Object invoke(Object proxy, Method method,							Object[] args) {						try {							System.out.println("被拦截的方法:" + method.getName());							return method.invoke(animal, args);						} catch (IllegalArgumentException e) {							// TODO Auto-generated catch block							e.printStackTrace();							return null;						} catch (IllegalAccessException e) {							// TODO Auto-generated catch block							e.printStackTrace();							return null;						} catch (InvocationTargetException e) {							// TODO Auto-generated catch block							e.printStackTrace();							return null;						}					}				});		if (proxyObj instanceof IAnimal) {			System.out.println("the proxyObj is an animal!");		} else {			System.out.println("the proxyObj isn't an animal!");		}		if (proxyObj instanceof Dog) {			System.out.println("the proxyObj is a dog!");		} else {			System.out.println("the proxyObj isn't a dog!");		}		IAnimal animalProxy = (IAnimal) proxyObj;		animalProxy.info();		animalProxy.hashCode();		System.out.println(animalProxy.getClass().getName().toString());	}}

程序执行的结果如下:

the proxyObj is an animal!

the proxyObj isn't a dog!

被拦截的方法:info
I am a dog!
被拦截的方法:hashCode
$Proxy0

从结果可以看出以下几点:

1. proxyObj 是一个实现了目标对象接口的对象,而不同于目标对象。也就是说,这种代理机制是面向接口,而不是面向类的。

2. info方法(在接口中)被成功拦截了,hashCode方法也成功被拦截了,但意外的是,getClass方法(继承自Object 类的方法)并没有被拦截!!

3. 应用调试还可以看出Invocation接口中invoke方法的传入的proxy参数确实就是代理对象实例proxyObj

为何getClass()没有被拦截?proxy参数又有何用呢?

先不管,做一个试验看看。既然这个proxy参数就是代理实例对象,它理所当然和proxyObj是一样的,可以调用info等方法。于是我们可以 在invoke方法中加上如下一条语句:

((IAnimal)proxy).info();

结果是:

the proxyObj is an animal!

the proxyObj isn't a dog!
被拦截的方法:info
被拦截的方法:info

.......

被拦截的方法:info

被拦截的方法:info

然后就是栈溢出

结果是很明显的,在invoke方法中调用proxy中的方法会再一次引发invoke方法,这就陷入了死循环,最终结果当然是栈溢出的。

可以在invoke方法中调用proxy.getClass(), 程序可以正常运行。但如果调用hashCode()方法同样会导致栈溢出。

通过上面的试验,可以得出一些初步结论,invoke 接口中的proxy参数不能用于调用所实现接口的方法。奇怪的是hashCode()和getClass()方法都是从Object中继承下来的方法,为 什么一个可以另一个不可以呢?带首疑问到doc文档看一下Object中这两个方法,发现getClass()是定义为final的,而 hashCode()不是。难道是这个原因,于是找到一个非final方法,如equals试了一下,真的又会导致栈溢出;找另一个final方法如 wait(),试了一下,invoke又不拦截了。final 难道就是关键之处?

还有一个问题就是proxy有什么用?既然proxy可以调用getClass()方法,我们就可以得到proxy的Class类象, 从而可以获得关于proxy代理实例的所有类信息,如方法列表,Annotation等,这就为我们提供的一个分析proxy的有力工具,如通过分析 Annotation分析方法的声明式事务需求。我想传入proxy参数应该是这样一个用意吧。

[转自:]

转载于:https://www.cnblogs.com/chenchong/archive/2012/09/13/2683218.html

你可能感兴趣的文章
luogu4849 寻找宝藏 (cdq分治+dp)
查看>>
Spring Cloud微服务笔记(五)Feign
查看>>
C语言键盘按键列表
查看>>
Codeforces Round #374 (Div. 2)
查看>>
oracle数据类型
查看>>
socket
查看>>
Vue中使用key的作用
查看>>
二叉索引树 树状数组
查看>>
日志框架--(一)基础篇
查看>>
Java设计模式之原型模式
查看>>
Spring学习(四)-----Spring Bean引用同xml和不同xml bean的例子
查看>>
哲理故事与管理之道(20)-用危机激励下属
查看>>
关于源程序到可运行程序的过程
查看>>
wepy的使用
查看>>
转载:mysql数据库密码忘记找回方法
查看>>
scratch少儿编程第一季——06、人在江湖混,没有背景怎么行。
查看>>
面向对象1
查看>>
在ns2.35中添加myevalvid框架
查看>>
【贪心+DFS】D. Field expansion
查看>>
为什么要使用href=”javascript:void(0);”
查看>>