代理模式有很多种,包括静态代理,保护代理,远程代理,动态代理等等,每一种都有特定的使用场景。本文主要介绍Java中的动态代理。
一、静态代理和动态代理的区别
首先给出静态代理和动态代理的定义:
静态代理:由程序员创建,或由工具自动生成源代码,然后进行编译。运行期间,代理的class文件已经存在。
动态代理:程序运行时,通过反射机制动态创建而成。
所谓代理模式,就是通过一个代理类,间接调用一个具体类的方法,这个具体的类,叫委托类。用另一种话描述,静态代理,是给每一个具体的类写一个代理类,需要用到具体类的时候,就给这个具体的类写一个代理类,然后调用代理类就行了。
但是这样,会有一个很大的缺点,如果有很多具体类,就需要些很多个代理类,这样无疑产生了很多重复代码。所以如果需要用一个代理类,完成所有的接口,就只能使用动态代理来完成。
二、动态代理示例
动态代理,使用了一个接口InvocationHandler,和一个代理类Proxy, 二者配合完成动态代理的功能。示例代码如下:
Interface Demo {
void sayHello();
}
class Demo implements IDemo {
@Override
public void sayHello() {
System.out.println("Hello World");
}
}
class DynamicInvoke implements InvocationHandler {
Object obj;
Object bind(Object obj) {
this.obj = obj;
return Proxy.newProxyInstance(
obj.getClass().getClasslocader(),
obj.getClass().getInterfaces(),
this
);
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
return method.invoke(obj,args);
}
}
}
class Client {
public static void main(String[] args) {
IDemo demo = (IDemo) new DynamicInvoke().bind(new Demo());
demo.sayHello();
}
上面的代码中,DynamicInvoke实现了InvocationHandler接口,也实现了invoke方法。同时,用Proxy.newProxyInstance()方法,创建了一个代理对象,那么使用这个代理对象,就可以调用具体类的方法。
对应上面的代码,bind()方法中,用Proxy.newProxyInstance()方法,创建了了一个代理对象,main()中是demo对象。使用demo.sayHello(),就可以调用具体类Demo的对象才能调用的sayHello()方法,就达到了效果。另外,如果又创建了一个实现了IDemo接口的Demo1具体类,那么只要在bind参数中,传入Demo1的对象就可以。
三、newProxyInstance方法说明
上面的代码中,比较有用的就两个,invoke()和newProxyInstance().
①invoke方法,第二个参数,Method类,Java反射reflection API中一部分,使用method.getName(),就可以知道proxy被调用的方法是什么。
public Object invoke(Object proxy, Method method, Object[] args) {
return method.invoke(obj,args); //obj是被调用的真正对象
}
</pre><span style="font-family:Microsoft YaHei; font-size:12px"><span style="color:rgb(0,153,0); font-family:'Microsoft YaHei'; font-size:12px"> ②</span><span style="font-family:'Microsoft YaHei'; font-size:12px"><span style="color:#3333ff">newProxyInstance()方法,</span></span></span><p></p><p><span style="font-family:Microsoft YaHei; font-size:12px"><span style="color:rgb(0,153,0); font-family:'Microsoft YaHei'; font-size:12px"> </span><span style="font-family:'Microsoft YaHei'; font-size:12px"><span style="color:#3333ff">第二个参数是一个接口数组。</span></span></span><span style="font-family:'Microsoft YaHei'; font-size:12px"><span style="color:#3333ff"> 该数组中只能有接口,不能有类。如果接口不是public的,那么必须属于同一个package,不同的接口内,不可以有名称和参数完全一样的方法。</span></span><span style="color:rgb(0,153,0); font-family:'Microsoft YaHei'; font-size:12px"> </span></p><p><span style="font-family:Microsoft YaHei; font-size:12px"></span></p><pre name="code" class="java"> Proxy.newProxyInstance(
obj.getClass().getClasslocader(),
obj.getClass().getInterfaces(),
this
);
跟踪这个方法,可以看到程序进行了验证,优化,缓存,生成字节码,显示加载等操作,最后调用了sun.misc.ProxyGenerator.generateProxyClass()来完成生成字节码的功能。