动态代理
1. 学习动态代理的目的
动态代理技术都是在框架中使用,例如Struts1、Struts2、Spring和Hibernate中都使用了动态代理技术。
我们学习动态代理技术,是为了更好的理解框架内部的原理,也是为了以后学习框架打下基础。
2. 运行时实现指定的接口
我们先回忆一下,如果要想实现一个接口,我们需要先写一个类,然后在类的后面给出“implements”XXX接口。这才是实现某个接口,例如:
接口:
public interface MyInterface {
void fun1();
void fun2();
}
实现:
public class MyInterfaceImpl implements MyInterface {
public void fun1() {
System.out.println("fun1()...");
}
public void fun2() {
System.out.println("fun2()...");
}
}
可是这样的代码太过于稀疏平常,我们要学习的动态代理技术就是可以通过一个方法调用就能生成一个指定接口的实现类对象。
例如:
Class[] interfaces= {MyInterface.class};
MyInterface mi = Proxy.newProxyInstance(loader, interfaces, h);
通过上面的代码,我们使用Proxy类的静态方法newProxyInstance()
就生成了一个对象,这个对象实现了interfaces数组中指定的接口。而返回值mi就是实现了接口MyInterface的实现类。
动态代理就是在运行的时候生成一个类,这个类会实现你指定的一组接口,而这个类没有.java文件,是在运行的时候生成的,我们也不用关心它是什么类型的,只需要知道它实现了哪些接口就可以了。
接下来,我们介绍一下newProxyInstance()方法的几个参数。
3. newProxyInstance()
方法
我们把文档上完整的方法拿过来看一下:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
我们可以看到,这是一个静态方法,返回值为Object,还有可能抛出IllegalArgumentException的异常。
但是我们着重要观察的是它的三个参数,也就是ClassLoader loader
,Class<?>[] interfaces
,InvocationHandler h
。
ClassLoader loader
:这是一个类加载器。
它是用来加载类的,可以把.class文件加载到内存中,形成Class对象。Class<?>[] interfaces
:这个是一个接口数组,这是要实现的接口们。InvocationHandler h
:这是调用处理器,这是一个接口,这个接口我们可以打开看一下,
这个接口只有一个方法,就是invoke()。public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
接下来,我们演示一下,怎么样来通过动态代理来得到两个接口的对象。
public class Demo {
@Test
public void fun1() {
// 接口数组
Class[] interfaces = { AInterface.class, BInterface.class };
// 类加载器
ClassLoader loader = this.getClass().getClassLoader();
// 调用处理器
InvocationHandler h = new InvocationHandler() {
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
System.out.println("你好,动态代理!");
return null;
}
};
// 使用三大参数创建代理对象
Object object = Proxy.newProxyInstance(loader, interfaces, h);
// 强制转换成AInterface、BInterface
AInterface aInterface = (AInterface) object;
BInterface bInterface = (BInterface) object;
// 调用实现类的方法
aInterface.a();
bInterface.b();
}
}
interface AInterface {
public void a();
}
interface BInterface {
public void b();
}
我们执行方法fun1()
,运行一下:
你好,动态代理!
你好,动态代理!
我们发现,我们执行了实现类的方法,其实就是在调用InvocationHandler
的invoke()
方法。
也就是说,代理对象的实现的所有的接口中的方法,内容都是调用InvocationHandler
的invoke()
方法。
InvocationHandler接口只有一个方法,即invoke()方法。它是对代理对象所有方法的唯一实现。也就是说,无论你调用代理对象上的哪个方法,其实都是在调用InvocationHandler
的invoke()
方法。
4. invoke()
方法的三个参数
我们可以注意到,上面的调用处理器InvocationHandler:
InvocationHandler h = new InvocationHandler() {
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
return null;
}
};
它有一个方法为invoke(),这个方法也有三个参数,分别为Object proxy
, Method method
,Object[] args
。接下来我们研究一下这三个参数。
我们先思考一个问题,这个invoke()方法在什么时候被调用?
答案是 在调用代理对象所实现接口中的方法时。
我们再做一个实验,我们在原来的接口里,添加一个方法,这个方法是有参数的。而且,我们再invoke()方法里面,输出一下这几个参数,代码:
public class Demo {
@Test
public void fun1() {
// 接口数组
Class[] interfaces = { AInterface.class };
// 类加载器
ClassLoader loader = this.getClass().getClassLoader();
// 调用处理器
InvocationHandler h = new InvocationHandler() {
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
// 打印输出 proxy的类
System.out.println("Object proxy:");
System.out.println(proxy.getClass());
// 打印method
System.out.println("Method method:");
System.out.println(method.toString());
// 打印args数组
System.out.println("Object[] args:");
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
return null;
}
};
// 使用三大参数创建代理对象
Object object = Proxy.newProxyInstance(loader, interfaces, h);
// 强制转换成AInterface、BInterface
AInterface aInterface = (AInterface) object;
// 调用实现类的方法
aInterface.aaa("HELLO", 100);
}
}
interface AInterface {
public void aaa(String s, int i);
}
我们看一下输出的结果:
Object proxy:
class com.veeja.demo.$Proxy2
Method method:
public abstract void com.veeja.demo.AInterface.aaa(java.lang.String,int)
Object[] args:
HELLO
100
我们可以稍微观察,得出一些信息:
proxy就是当前对象,也就是所谓的代理对象,jvm给出的值为com.veeja.demo.$Proxy2
。
而method是当前被调用的方法(目标方法)。
最后的args,就是参数列表。
我们可以用一张图,来对应一下这些内容的关系:
而invoke()方法的返回值,其实就是调用接口实现类方法得到的内容。
END.